mirror of
https://github.com/neosubhamoy/pytubepp.git
synced 2026-02-04 10:22:21 +05:30
Compare commits
36 Commits
v1.1.1-sta
...
v1.1.8-sta
120
README.md
120
README.md
@@ -9,18 +9,28 @@
|
|||||||
[](https://github.com/neosubhamoy/pytubepp/)
|
[](https://github.com/neosubhamoy/pytubepp/)
|
||||||
[](https://github.com/neosubhamoy/pytubepp/)
|
[](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.
|
😀 NEWS: If you are not a power 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.
|
||||||
|
|
||||||
|
> **🥰 Liked this project? Please consider giving it a Star (🌟) on github to show us your appreciation and help the algorythm recommend this project to even more awesome people like you!**
|
||||||
|
|
||||||
|
### **💻 Supported Platforms**
|
||||||
|
- Windows
|
||||||
|
- Linux
|
||||||
|
- MacOS
|
||||||
|
- Android (Termux)
|
||||||
|
|
||||||
### **🏷️ Features**
|
### **🏷️ Features**
|
||||||
* Auto Post-Process & Merge YouTube DASH Streams
|
* Auto Post-Process & Merge YouTube DASH Streams
|
||||||
* Supports upto 8K 60fps HDR Stream Download
|
* Supports upto 8K 60fps HDR Stream Download
|
||||||
* Supports MP3 Download (with Embeded Thumbnail and Tags)
|
* Supports MP3 Download (with Embeded Thumbnail and Tags)
|
||||||
|
* Supports Embeded Captions
|
||||||
* Smart Stream Selection
|
* Smart Stream Selection
|
||||||
* Highly Configurable and Many More 😉
|
* Highly Configurable and Many More 😉
|
||||||
|
|
||||||
### **📎 Pre-Requirements**
|
### **📎 Pre-Requirements**
|
||||||
* [Python](https://www.python.org/downloads/) (>=3.8)
|
* [Python](https://www.python.org/downloads/) (>=3.8)
|
||||||
* [FFmpeg](https://ffmpeg.org/)
|
* [FFmpeg](https://ffmpeg.org/)
|
||||||
|
* [Node.js](https://nodejs.org/en/download/)
|
||||||
|
|
||||||
### **🧩 Python Dependencies**
|
### **🧩 Python Dependencies**
|
||||||
* [pytubefix](https://pypi.org/project/pytubefix/)
|
* [pytubefix](https://pypi.org/project/pytubefix/)
|
||||||
@@ -33,6 +43,11 @@
|
|||||||
* [setuptools](https://pypi.org/project/setuptools/)
|
* [setuptools](https://pypi.org/project/setuptools/)
|
||||||
|
|
||||||
### **🛠️ Installation**
|
### **🛠️ Installation**
|
||||||
|
|
||||||
|
Open a Terminal/CMD (CLI) window and run the following commands one after one step by step (based on your OS) to install 'PytubePP' in your system!
|
||||||
|
|
||||||
|
> If you want to follow along make sure [WinGet](https://learn.microsoft.com/en-us/windows/package-manager/winget/#install-winget) is enabled if you are on Windows (verify using `winget --version`), install [Homebrew](https://brew.sh/) if you are on MacOS and install [Termux](https://termux.dev/) if you are on Android
|
||||||
|
|
||||||
1. Install Python and PIP
|
1. Install Python and PIP
|
||||||
- Linux (Debian): Python is pre-installed install PIP using `sudo apt install python3-pip`<br>
|
- Linux (Debian): Python is pre-installed install PIP using `sudo apt install python3-pip`<br>
|
||||||
- Linux (Fedora): Python is pre-installed install PIP using `sudo dnf install python3-pip`<br>
|
- Linux (Fedora): Python is pre-installed install PIP using `sudo dnf install python3-pip`<br>
|
||||||
@@ -40,36 +55,64 @@
|
|||||||
- Windows (10/11): `winget install Python.Python.3.13`<br>
|
- Windows (10/11): `winget install Python.Python.3.13`<br>
|
||||||
- MacOS (using Homebrew): `brew install python`<br>
|
- MacOS (using Homebrew): `brew install python`<br>
|
||||||
- Android (using Termux): `pkg install python`
|
- Android (using Termux): `pkg install python`
|
||||||
|
|
||||||
|
> You can skip step 2, 3 and auto install them later using the command `pytubepp --postinstall` post installation (works in: Windows, Linux - debian fedora arch, MacOS)
|
||||||
|
|
||||||
2. Install FFmpeg
|
2. Install FFmpeg
|
||||||
- Linux (Debian): `sudo apt install ffmpeg`<br>
|
- Linux (Debian): `sudo apt install ffmpeg`<br>
|
||||||
- Linux (Fedora): `sudo dnf install ffmpeg-free`<br>
|
- Linux (Fedora) ([enable](https://docs.fedoraproject.org/en-US/quick-docs/rpmfusion-setup/#_enabling_the_rpm_fusion_repositories_using_command_line_utilities) rpmfusion free+nonfree repos before installing): `sudo dnf install ffmpeg`<br>
|
||||||
- Linux (Arch): `sudo pacman -S ffmpeg`<br>
|
- Linux (Arch): `sudo pacman -S ffmpeg`<br>
|
||||||
- Windows (10/11): `winget install ffmpeg`<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`
|
||||||
3. Install PytubePP (using PIP)
|
3. Install Node.js
|
||||||
|
- Linux (Debian): `sudo apt install nodejs`<br>
|
||||||
|
- Linux (Fedora): `sudo dnf install nodejs`<br>
|
||||||
|
- Linux (Arch): `sudo pacman -S nodejs-lts-iron npm`<br>
|
||||||
|
- Windows (10/11): `winget install OpenJS.NodeJS.LTS`<br>
|
||||||
|
- MacOS (using Homebrew): `brew install node`<br>
|
||||||
|
- Android (using Termux): `pkg install nodejs`
|
||||||
|
4. Install PytubePP (using PIP)
|
||||||
|
|
||||||
|
> 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
|
||||||
```
|
```
|
||||||
|
|
||||||
**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)**
|
**UPDATE: 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)**
|
||||||
|
|
||||||
```
|
```terminal
|
||||||
pip install pytubefix pytubepp --upgrade
|
pip install pytubefix pytubepp --upgrade
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> It is highly recommended to run the post install script once after updating 'PytubePP' using: `pytubepp --postinstall` command
|
||||||
|
|
||||||
|
**UNINSTALL: If you want to uninstall PytubePP (Use the command below to uninstall) NOTE: it will only remove the 'PytubePP' python package**
|
||||||
|
```terminal
|
||||||
|
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 maximum available resolution 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:
|
||||||
|
```terminal
|
||||||
|
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
|
||||||
@@ -78,21 +121,57 @@ pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -s mp3
|
|||||||
```terminal
|
```terminal
|
||||||
pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -i
|
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).
|
* To cancel/stop an ongoing download press `CTRL` + `C` on keyboard (it is highly recommended to run the `pytubepp -ct` command once after canceling an ongoing download).
|
||||||
|
|
||||||
|
* To set default stream (suppose 1080p) use: `pytubepp -ds 1080p` command (This is useful when you always preffer to download this stream even if higher resolution stream is available. If You set default stream then next time when you download, You don't need to pass the `-s 1080p` flag, just pass the video url and it will auto select the `1080p` stream by default).
|
||||||
|
|
||||||
|
* To set default caption (suppose en - English) use: `pytubepp -dc en` command (Useful when you always preffer to embed caption in videos and in a specific language, If You set default caption, You don't need to pass `-c en` flag in next downloads just pass the video url).
|
||||||
|
|
||||||
|
* You can also view all these current configurations using: `pytubepp -sc` command and reset them using: `pytubepp -r` if needed.
|
||||||
|
|
||||||
* List of all available flags are given below:
|
* List of all available flags are given below:
|
||||||
|
|
||||||
| Flag | Usage | Requires Parameter | Requires URL | Parameters | Default |
|
| Short Flag | 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 | --stream | 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, Publication Date, Duration, Available Download Streams | NO | YES | No parameters | No default |
|
| -c | --caption | Choose preferred caption | YES | YES | All [ISO 639-1 Language Codes](https://www.w3schools.com/tags/ref_language_codes.asp) + auto generated ones + `none` for No Caption (Pass any one of them) eg: `en` for English | Your chosen Default Caption via `-dc` flag |
|
||||||
| -ri | Shows the video information in raw json format | NO | YES | No parameters | No default |
|
| -i | --show-info | Shows the video information like: Title, Author, Views, Publication Date, Duration, Available Download Streams and Captions | NO | YES | No parameters | No default |
|
||||||
| -jp | Shows raw json output in prettified view (with indentation: 4) (primarily used with -ri flag)| NO | YES | No parameters | No default |
|
| -ls | --list-stream | Lists all available streams (video, audio, caption) (only for debuging purposes) | NO | YES | No parameters | No default |
|
||||||
| -ds | Set default download stream | YES | NO | `144p` `240p` `360p` `480p` `720p` `1080p` `1440p` `2160p` `4320p` `mp3` `max` (Pass any one of them) | `max` |
|
| -ri | --raw-info | Shows the video information in raw json format | NO | YES | No parameters | No default |
|
||||||
| -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 `PytubePP Downloads` folder in your System's `Downloads` folder |
|
| -jp | --json-prettify | Shows raw json output in prettified view (with indentation: 4) (primarily used with -ri flag)| NO | YES | No parameters | No default |
|
||||||
| -r | Reset to default configuration (Download Folder, Default Stream) | NO | NO | No parameters | No default |
|
| -ds | --default-stream | Set default download stream | YES | NO | `144p` `240p` `360p` `480p` `720p` `1080p` `1440p` `2160p` `4320p` `mp3` `max` (Pass any one of them) | `max` |
|
||||||
| -sc | Show all current user configurations | NO | NO | No parameters | No default |
|
| -dc | --default-caption | Set default caption | YES | NO | All [ISO 639-1 Language Codes](https://www.w3schools.com/tags/ref_language_codes.asp) + auto generated ones + `none` for No Caption (Pass any one of them) eg: `en` for English | `none` |
|
||||||
| -ct | Clear temporary files (audio, video, thumbnail) of the failed, incomplete downloads | NO | NO | No parameters | No default |
|
| -df | --download-folder | 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 `PytubePP Downloads` folder in your System's `Downloads` folder |
|
||||||
|
| -r | --reset-default | Reset to default configuration (Download Folder, Default Stream, Default Caption) | NO | NO | No parameters | No default |
|
||||||
|
| -sc | --show-config | Show all current user configurations | NO | NO | No parameters | No default |
|
||||||
|
| -ct | --clear-temp | Clear temporary files (audio, video, thumbnail, caption) of the failed, incomplete downloads | NO | NO | No parameters | No default |
|
||||||
|
| -pi | --postinstall | Auto install all external dependencies (FFmpeg, Node.js) (works in Windows, Linux - debian fedora arch, MacOS) | NO | NO | No parameters | No default |
|
||||||
|
|
||||||
|
### 🛠️ Contributing / Building from Source
|
||||||
|
|
||||||
|
Want to be part of this? Feel free to contribute...!! Pull Requests are always welcome...!! (^_^) Follow these simple steps to start building:
|
||||||
|
|
||||||
|
* Make sure to install Python, FFmpeg, Node.js and Git before proceeding.
|
||||||
|
|
||||||
|
1. Fork this repo in your github account.
|
||||||
|
2. Git clone the forked repo in your local machine.
|
||||||
|
|
||||||
|
> Use `python3` and `pip3` commands instead of `python` and `pip` if you are on Linux or MacOS.
|
||||||
|
|
||||||
|
3. Create a Python virtual environment (venv) and activate it (Optional)
|
||||||
|
4. Install python dependencies
|
||||||
|
|
||||||
|
```terminal
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
5. build, install and test the module
|
||||||
|
|
||||||
|
```terminal
|
||||||
|
python -m build // build the module
|
||||||
|
|
||||||
|
pip install .\dist\pytubepp-<version>-py3-none-any.whl // install the module (give the path to the newly genrated whl file based on your OS path style and don't forget to replace the <version> with the actual version number)
|
||||||
|
```
|
||||||
|
6. Do the changes, Send a Pull Request with proper Description (NOTE: Pull Requests Without Proper Description will be Rejected)
|
||||||
|
|
||||||
⭕ Noticed any Bugs? or Want to give me some suggetions? always feel free to open an issue...!!
|
⭕ Noticed any Bugs? or Want to give me some suggetions? always feel free to open an issue...!!
|
||||||
|
|
||||||
@@ -100,8 +179,7 @@ pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -i
|
|||||||
|
|
||||||
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**
|
⚖️ NOTE: YouTube is a trademark of Google LLC. Use of this trademark is subject to Google Permissions. Downloading and using Copyrighted YouTube Content for Commercial pourposes are not allowed by YouTube Terms without proper Permissions from the Creator. We don't promote this kinds of activity, You should use the downloaded contents wisely and at your own responsibility.
|
||||||
<br></br>
|
|
||||||
|
|
||||||
****
|
****
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pytubepp"
|
name = "pytubepp"
|
||||||
version = "1.1.1"
|
version = "1.1.8"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="Subhamoy Biswas", email="hey@neosubhamoy.com" },
|
{ name="Subhamoy Biswas", email="hey@neosubhamoy.com" },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ def get_download_folder():
|
|||||||
DEFAULT_CONFIG = {
|
DEFAULT_CONFIG = {
|
||||||
'downloadDIR': get_download_folder(),
|
'downloadDIR': get_download_folder(),
|
||||||
'defaultStream': 'max',
|
'defaultStream': 'max',
|
||||||
|
'defaultCaption': 'none',
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_temporary_directory():
|
def get_temporary_directory():
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
from .config import get_temporary_directory, load_config
|
from .config import get_temporary_directory, load_config
|
||||||
from .utils import get_unique_filename
|
from .utils import get_unique_filename, postprocess_cleanup, unpack_caption
|
||||||
import os, re, requests, shutil, sys, random
|
import os, re, requests, shutil, sys, random, ffmpy
|
||||||
|
|
||||||
userConfig = load_config()
|
userConfig = load_config()
|
||||||
downloadDIR = userConfig['downloadDIR']
|
downloadDIR = userConfig['downloadDIR']
|
||||||
tempDIR = get_temporary_directory()
|
tempDIR = get_temporary_directory()
|
||||||
|
|
||||||
def download_progressive(stream, itag, title, resolution, file_extention, tempDIR=tempDIR, downloadDIR=downloadDIR):
|
def download_progressive(stream, itag, title, resolution, file_extention, captions, caption_code=None, tempDIR=tempDIR, downloadDIR=downloadDIR):
|
||||||
global total_filesize, progress_bar
|
global total_filesize, progress_bar
|
||||||
selected_vdo = stream.get_by_itag(itag)
|
selected_vdo = stream.get_by_itag(itag)
|
||||||
total_filesize = selected_vdo.filesize
|
total_filesize = selected_vdo.filesize
|
||||||
@@ -15,8 +15,29 @@ def download_progressive(stream, itag, title, resolution, file_extention, tempDI
|
|||||||
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)) if not caption_code else os.path.join(downloadDIR, get_unique_filename(title + '_' + resolution + '_' + caption_code + '.' + file_extention))
|
||||||
selected_vdo.download(output_path=tempDIR, filename=filename)
|
selected_vdo.download(output_path=tempDIR, filename=filename)
|
||||||
|
|
||||||
|
if caption_code:
|
||||||
|
print(f'Downloading Caption ({caption_code})...')
|
||||||
|
caption = captions[caption_code]
|
||||||
|
_, caption_lang = unpack_caption(caption)
|
||||||
|
caption_file = os.path.join(tempDIR, random_filename + '_cap.srt')
|
||||||
|
caption.save_captions(caption_file)
|
||||||
|
print('Processing...')
|
||||||
|
devnull = open(os.devnull, 'w')
|
||||||
|
output_temp_file_with_subs = os.path.join(tempDIR, random_filename + '_merged.' + file_extention)
|
||||||
|
ff = ffmpy.FFmpeg(
|
||||||
|
inputs={output_temp_file: None},
|
||||||
|
outputs={output_temp_file_with_subs: ['-i', caption_file, '-c', 'copy', '-c:s', 'mov_text', '-metadata:s:s:0', f'language={caption_code}', '-metadata:s:s:0', f'title={caption_lang}', '-metadata:s:s:0', f'handler_name={caption_lang}']}
|
||||||
|
)
|
||||||
|
ff.run(stdout=devnull, stderr=devnull)
|
||||||
|
devnull.close()
|
||||||
|
|
||||||
|
shutil.move(output_temp_file_with_subs, output_file)
|
||||||
|
postprocess_cleanup(tempDIR, ['_vdo.' + file_extention, '_cap.srt', '_merged.' + file_extention], random_filename)
|
||||||
|
print('Done! 🎉')
|
||||||
|
else:
|
||||||
print('Processing...')
|
print('Processing...')
|
||||||
shutil.move(output_temp_file, output_file)
|
shutil.move(output_temp_file, output_file)
|
||||||
print('Done! 🎉')
|
print('Done! 🎉')
|
||||||
|
|||||||
619
pytubepp/main.py
619
pytubepp/main.py
@@ -1,28 +1,405 @@
|
|||||||
|
from pytubefix import YouTube
|
||||||
|
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 .utils import get_version, clear_temp_files
|
from .download import download_progressive, download_nonprogressive, download_audio, progress
|
||||||
from .video import set_global_video_info, show_video_info, show_raw_info, download_stream
|
from .postprocess import merge_audio_video, convert_to_mp3
|
||||||
import appdirs, os, sys, argparse
|
from .utils import get_version, clear_temp_files, is_valid_url, network_available, ffmpeg_installed, nodejs_installed, unpack_caption
|
||||||
|
from .postinstaller import postinstall
|
||||||
|
import appdirs, os, re, sys, argparse, json
|
||||||
|
|
||||||
global stream, maxres
|
class YouTubeDownloader:
|
||||||
userConfig = load_config()
|
def __init__(self):
|
||||||
downloadDIR = userConfig['downloadDIR']
|
self.user_config = load_config()
|
||||||
tempDIR = get_temporary_directory()
|
self.download_dir = self.user_config['downloadDIR']
|
||||||
configDIR = appdirs.user_config_dir('pytubepp')
|
self.temp_dir = get_temporary_directory()
|
||||||
defaultStream = userConfig['defaultStream']
|
self.config_dir = appdirs.user_config_dir('pytubepp')
|
||||||
version = get_version()
|
self.default_stream = self.user_config['defaultStream']
|
||||||
|
self.default_caption = self.user_config['defaultCaption']
|
||||||
|
self.version = get_version()
|
||||||
|
|
||||||
|
# Video attributes
|
||||||
|
self.video = None
|
||||||
|
self.author = None
|
||||||
|
self.title = None
|
||||||
|
self.thumbnail = None
|
||||||
|
self.views = None
|
||||||
|
self.stream = None
|
||||||
|
self.maxres = None
|
||||||
|
|
||||||
|
self.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]']}
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_video_info(self, link):
|
||||||
|
if not network_available():
|
||||||
|
print('\nRequest timeout! Please check your network and try again...!!')
|
||||||
|
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, by running: pytubepp --postinstall or read https://github.com/neosubhamoy/pytubepp#%EF%B8%8F-installation for manual instructions\n")
|
||||||
|
|
||||||
|
if is_valid_url(link):
|
||||||
|
link = is_valid_url(link).group(1)
|
||||||
|
self.video = YouTube(link, 'WEB', on_progress_callback=progress)
|
||||||
|
self.author = self.video.author
|
||||||
|
self.title = re.sub(r'[\\/*?:"<>|]', '_', self.author + ' - ' + self.video.title)
|
||||||
|
self.thumbnail = self.video.thumbnail_url
|
||||||
|
self.views = str(self.video.views)
|
||||||
|
self.stream = self.video.streams
|
||||||
|
self.captions = self.video.captions
|
||||||
|
|
||||||
|
# Find maximum resolution
|
||||||
|
for res in self.stream_resolutions.keys():
|
||||||
|
if res != 'mp3' and self.stream.filter(res=res):
|
||||||
|
self.maxres = res
|
||||||
|
break
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_stream_info(self, res, matching_stream):
|
||||||
|
"""Helper method to get stream information based on resolution"""
|
||||||
|
stream_info = {}
|
||||||
|
|
||||||
|
if res == 'mp3':
|
||||||
|
stream_info = {
|
||||||
|
'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",
|
||||||
|
'raw_filesize': matching_stream.filesize,
|
||||||
|
'fps': None,
|
||||||
|
'raw_fps': None,
|
||||||
|
'vdo_codec': None,
|
||||||
|
'ado_codec': matching_stream.audio_codec,
|
||||||
|
'vdo_bitrate': None,
|
||||||
|
'ado_bitrate': matching_stream.abr
|
||||||
|
}
|
||||||
|
elif res == '360p':
|
||||||
|
stream_info = {
|
||||||
|
'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",
|
||||||
|
'raw_filesize': matching_stream.filesize,
|
||||||
|
'fps': f"{matching_stream.fps}fps",
|
||||||
|
'raw_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
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
_select_suitable_audio_stream = lambda stream: 251 if stream.mime_type == 'video/webm' else 140
|
||||||
|
# Check for HDR variants first
|
||||||
|
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))
|
||||||
|
|
||||||
|
# Use HDR stream if available, otherwise use the original stream
|
||||||
|
final_stream = hdr_stream if hdr_stream else matching_stream
|
||||||
|
|
||||||
|
# For 720p, check if HigherFps MP4 version exists and prefer it
|
||||||
|
if res == '720p' and not hdr_stream:
|
||||||
|
higher_fps_stream = self.stream.get_by_itag(298)
|
||||||
|
if higher_fps_stream:
|
||||||
|
final_stream = higher_fps_stream
|
||||||
|
|
||||||
|
audio_stream = self.stream.get_by_itag(_select_suitable_audio_stream(final_stream))
|
||||||
|
|
||||||
|
total_size = final_stream.filesize + audio_stream.filesize
|
||||||
|
|
||||||
|
stream_info = {
|
||||||
|
'type': final_stream.mime_type,
|
||||||
|
'filesize': f"{total_size / (1024 * 1024 * 1024):.2f} GB" if total_size >= 1073741824 else f"{total_size / (1024 * 1024):.2f} MB",
|
||||||
|
'raw_filesize': total_size,
|
||||||
|
'fps': f"{final_stream.fps}fps",
|
||||||
|
'raw_fps': final_stream.fps,
|
||||||
|
'vdo_codec': final_stream.video_codec,
|
||||||
|
'ado_codec': audio_stream.audio_codec,
|
||||||
|
'vdo_bitrate': f"{final_stream.bitrate / 1024:.0f}kbps",
|
||||||
|
'ado_bitrate': audio_stream.abr,
|
||||||
|
'is_hdr': bool(hdr_stream), # Track if this is an HDR stream
|
||||||
|
'stream_itag': final_stream.itag # Track the actual itag being used
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream_info
|
||||||
|
|
||||||
|
def show_video_info(self, link):
|
||||||
|
if self.set_video_info(link):
|
||||||
|
table = []
|
||||||
|
found = False
|
||||||
|
|
||||||
|
for res in self.stream_resolutions.keys():
|
||||||
|
if found or (res not in ['mp3'] and self.stream.filter(res=res)) or (res == 'mp3' and self.stream.get_by_itag(140)):
|
||||||
|
found = True
|
||||||
|
matching_stream = self.stream.get_by_itag(140) if res == 'mp3' else next((s for s in self.stream if s.resolution == res), None)
|
||||||
|
|
||||||
|
if matching_stream:
|
||||||
|
stream_info = self.get_stream_info(res, matching_stream)
|
||||||
|
message = self.stream_resolutions[res]['message'] + [
|
||||||
|
stream_info['type'],
|
||||||
|
stream_info['filesize'],
|
||||||
|
stream_info['fps'] if stream_info['fps'] else "none",
|
||||||
|
stream_info['vdo_codec'] if stream_info['vdo_codec'] else "none",
|
||||||
|
stream_info['ado_codec'],
|
||||||
|
stream_info['vdo_bitrate'] if stream_info['vdo_bitrate'] else "none",
|
||||||
|
stream_info['ado_bitrate']
|
||||||
|
]
|
||||||
|
table.append(message)
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
print('Sorry, No video streams found....!!!')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
print(f'\nTitle: {self.video.title}\nAuthor: {self.author}\nPublished On: {self.video.publish_date.strftime("%d/%m/%Y")}\nDuration: {f"{self.video.length//3600:02}:{(self.video.length%3600)//60:02}:{self.video.length%60:02}" if self.video.length >= 3600 else f"{(self.video.length%3600)//60:02}:{self.video.length%60:02}"}\nViews: {self.views}\nCaptions: {"Available" if self.captions else "Unavailable"}')
|
||||||
|
|
||||||
|
print('\n')
|
||||||
|
print(tabulate(table, headers=['Stream', 'Alias (for -s flag)', 'Format', 'Size', 'FrameRate', 'V-Codec', 'A-Codec', 'V-BitRate', 'A-BitRate']))
|
||||||
|
print('\n')
|
||||||
|
|
||||||
|
if self.captions:
|
||||||
|
caption_table = []
|
||||||
|
for caption in self.captions:
|
||||||
|
cap_code, cap_lang = unpack_caption(caption)
|
||||||
|
caption_table.append([cap_lang, cap_code])
|
||||||
|
print(tabulate(caption_table, headers=['Caption', 'CaptionCode (for -c flag)']))
|
||||||
|
print('\n')
|
||||||
|
else:
|
||||||
|
print('\nInvalid video link! Please enter a valid video url...!!')
|
||||||
|
|
||||||
|
def show_all_streams(self, link):
|
||||||
|
if self.set_video_info(link):
|
||||||
|
print(f"Available Streams({len(self.stream)}):")
|
||||||
|
if self.stream:
|
||||||
|
for stream in self.stream:
|
||||||
|
print(stream)
|
||||||
|
else:
|
||||||
|
print('No stream available!')
|
||||||
|
|
||||||
|
print(f"\nAvailable Captions({len(self.captions)}):")
|
||||||
|
if self.captions:
|
||||||
|
for caption in self.captions:
|
||||||
|
print(caption)
|
||||||
|
else:
|
||||||
|
print('No caption available!')
|
||||||
|
else:
|
||||||
|
print('\nInvalid video link! Please enter a valid video url...!!')
|
||||||
|
|
||||||
|
def show_raw_info(self, link, prettify=False):
|
||||||
|
if self.set_video_info(link):
|
||||||
|
streams_list = []
|
||||||
|
found = False
|
||||||
|
|
||||||
|
for res in self.stream_resolutions.keys():
|
||||||
|
if found or (res not in ['mp3'] and self.stream.filter(res=res)) or (res == 'mp3' and self.stream.get_by_itag(140)):
|
||||||
|
found = True
|
||||||
|
matching_stream = self.stream.get_by_itag(140) if res == 'mp3' else next((s for s in self.stream if s.resolution == res), None)
|
||||||
|
|
||||||
|
if matching_stream:
|
||||||
|
stream_info = self.get_stream_info(res, matching_stream)
|
||||||
|
streams_list.append({
|
||||||
|
'itag': stream_info.get('stream_itag', matching_stream.itag),
|
||||||
|
'res': res,
|
||||||
|
'mime_type': stream_info['type'],
|
||||||
|
'file_size': stream_info['raw_filesize'],
|
||||||
|
'fps': stream_info['raw_fps'],
|
||||||
|
'vcodec': stream_info['vdo_codec'],
|
||||||
|
'acodec': stream_info['ado_codec'],
|
||||||
|
'vbitrate': stream_info['vdo_bitrate'],
|
||||||
|
'abitrate': stream_info['ado_bitrate'],
|
||||||
|
'is_hdr': stream_info.get('is_hdr', False)
|
||||||
|
})
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
print('Sorry, No video streams found....!!!')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
captions_list = []
|
||||||
|
if self.captions:
|
||||||
|
for caption in self.captions:
|
||||||
|
cap_code, cap_lang = unpack_caption(caption)
|
||||||
|
captions_list.append({
|
||||||
|
'code': cap_code,
|
||||||
|
'lang': cap_lang
|
||||||
|
})
|
||||||
|
|
||||||
|
output = {
|
||||||
|
'id': self.video.video_id,
|
||||||
|
'title': self.video.title,
|
||||||
|
'author': self.author,
|
||||||
|
'thumbnail_url': self.thumbnail,
|
||||||
|
'views': self.video.views,
|
||||||
|
'published_on': self.video.publish_date.strftime('%d/%m/%Y'),
|
||||||
|
'duration': self.video.length,
|
||||||
|
'streams': streams_list,
|
||||||
|
'captions': captions_list or None
|
||||||
|
}
|
||||||
|
|
||||||
|
print(json.dumps(output, indent=4 if prettify else None))
|
||||||
|
else:
|
||||||
|
print('\nInvalid video link! Please enter a valid video url...!!')
|
||||||
|
|
||||||
|
def get_allowed_streams(self, link):
|
||||||
|
if self.set_video_info(link):
|
||||||
|
allowed_streams = []
|
||||||
|
found = False
|
||||||
|
for res in self.stream_resolutions.keys():
|
||||||
|
if found or (res not in ['mp3'] and self.stream.filter(res=res)) or (res == 'mp3' and self.stream.get_by_itag(140)):
|
||||||
|
found = True
|
||||||
|
allowed_streams.extend(self.stream_resolutions[res]['allowed_streams'])
|
||||||
|
return allowed_streams
|
||||||
|
else:
|
||||||
|
print('\nInvalid video link! Please enter a valid video url...!!')
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_allowed_captions(self, link):
|
||||||
|
if self.set_video_info(link):
|
||||||
|
return self.captions.keys()
|
||||||
|
else:
|
||||||
|
print('\nInvalid video link! Please enter a valid video url...!!')
|
||||||
|
return []
|
||||||
|
|
||||||
|
def print_short_info(self, chosen_stream, chosen_caption=None):
|
||||||
|
print(f'\nTitle: {self.title}')
|
||||||
|
|
||||||
|
if chosen_stream == 'mp3':
|
||||||
|
print(f'Selected: Audio [128kbps (140)] --> (MP3)')
|
||||||
|
return
|
||||||
|
|
||||||
|
if chosen_stream in ['360', '360p']:
|
||||||
|
print(f"Selected: Video [360p (18)] + Audio [96kbps (18)]{f' + Caption [{chosen_caption}]' if chosen_caption else ''} --> (MP4)")
|
||||||
|
return
|
||||||
|
|
||||||
|
_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)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
if res == '720p' and not hdr_stream:
|
||||||
|
high_fps_stream = self.stream.get_by_itag(298)
|
||||||
|
if high_fps_stream:
|
||||||
|
matching_stream = high_fps_stream
|
||||||
|
|
||||||
|
audio_stream = self.stream.get_by_itag(_select_suitable_audio_stream(matching_stream))
|
||||||
|
|
||||||
|
output_format = 'MP4'
|
||||||
|
if matching_stream.mime_type == 'video/webm':
|
||||||
|
output_format = 'WEBM'
|
||||||
|
|
||||||
|
print(f"Selected: Video [{res} ({matching_stream.itag})] + Audio [{audio_stream.abr} ({audio_stream.itag})]{f' + Caption [{chosen_caption}]' if chosen_caption else ''} --> ({output_format})")
|
||||||
|
|
||||||
|
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, by running: pytubepp --postinstall or read https://github.com/neosubhamoy/pytubepp#%EF%B8%8F-installation for manual instructions\n")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if self.set_video_info(link):
|
||||||
|
allowed_streams = self.get_allowed_streams(link)
|
||||||
|
allowed_captions = self.get_allowed_captions(link)
|
||||||
|
|
||||||
|
if chosen_caption and (chosen_caption not in allowed_captions):
|
||||||
|
print('\nInvalid caption code or caption not available! Please choose a different caption...!! (use -i to see available captions)')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if chosen_stream in allowed_streams:
|
||||||
|
self.print_short_info(chosen_stream, chosen_caption)
|
||||||
|
if chosen_stream in ['360', '360p']:
|
||||||
|
download_progressive(self.stream, 18, self.title, '360p', 'mp4', self.captions, chosen_caption)
|
||||||
|
elif chosen_stream in ['1080', '1080p', 'fhd']:
|
||||||
|
self._handle_1080p_download(chosen_caption)
|
||||||
|
elif chosen_stream in ['720', '720p', 'hd']:
|
||||||
|
self._handle_720p_download(chosen_caption)
|
||||||
|
elif chosen_stream in ['480', '480p']:
|
||||||
|
merge_audio_video(self.title, '480p', 'mp4', download_nonprogressive(self.stream, 135, 140, 'mp4', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
elif chosen_stream in ['240', '240p']:
|
||||||
|
merge_audio_video(self.title, '240p', 'mp4', download_nonprogressive(self.stream, 133, 140, 'mp4', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
elif chosen_stream in ['144', '144p']:
|
||||||
|
merge_audio_video(self.title, '144p', 'mp4', download_nonprogressive(self.stream, 160, 140, 'mp4', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
elif chosen_stream in ['4320', '4320p', '8k']:
|
||||||
|
self._handle_4320p_download(chosen_caption)
|
||||||
|
elif chosen_stream in ['2160', '2160p', '4k']:
|
||||||
|
self._handle_2160p_download(chosen_caption)
|
||||||
|
elif chosen_stream in ['1440', '1440p', '2k']:
|
||||||
|
self._handle_1440p_download(chosen_caption)
|
||||||
|
elif chosen_stream == 'mp3':
|
||||||
|
convert_to_mp3(self.title, self.thumbnail, download_audio(self.stream, 140, self.temp_dir), self.author, self.video.title, self.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 _handle_4320p_download(self, chosen_caption=None):
|
||||||
|
if self.stream.get_by_itag(702):
|
||||||
|
merge_audio_video(self.title, '8k', 'mp4', download_nonprogressive(self.stream, 702, 140, 'mp4', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
elif self.stream.get_by_itag(571):
|
||||||
|
merge_audio_video(self.title, '8k', 'mp4', download_nonprogressive(self.stream, 571, 140, 'mp4', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
|
||||||
|
def _handle_2160p_download(self, chosen_caption=None):
|
||||||
|
if self.stream.get_by_itag(701):
|
||||||
|
merge_audio_video(self.title, '4k', 'mp4', download_nonprogressive(self.stream, 701, 140, 'mp4', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
elif self.stream.get_by_itag(315):
|
||||||
|
merge_audio_video(self.title, '4k', 'webm', download_nonprogressive(self.stream, 315, 251, 'webm', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
elif self.stream.get_by_itag(313):
|
||||||
|
merge_audio_video(self.title, '4k', 'webm', download_nonprogressive(self.stream, 313, 251, 'webm', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
|
||||||
|
def _handle_1440p_download(self, chosen_caption=None):
|
||||||
|
if self.stream.get_by_itag(700):
|
||||||
|
merge_audio_video(self.title, '2k', 'mp4', download_nonprogressive(self.stream, 700, 140, 'mp4', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
elif self.stream.get_by_itag(308):
|
||||||
|
merge_audio_video(self.title, '2k', 'webm', download_nonprogressive(self.stream, 308, 251, 'webm', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
elif self.stream.get_by_itag(271):
|
||||||
|
merge_audio_video(self.title, '2k', 'webm', download_nonprogressive(self.stream, 271, 251, 'webm', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
|
||||||
|
def _handle_1080p_download(self, chosen_caption=None):
|
||||||
|
if self.stream.get_by_itag(699):
|
||||||
|
merge_audio_video(self.title, '1080p', 'mp4', download_nonprogressive(self.stream, 699, 140, 'mp4', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
elif self.stream.get_by_itag(299):
|
||||||
|
merge_audio_video(self.title, '1080p', 'mp4', download_nonprogressive(self.stream, 299, 140, 'mp4', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
elif self.stream.get_by_itag(137):
|
||||||
|
merge_audio_video(self.title, '1080p', 'mp4', download_nonprogressive(self.stream, 137, 140, 'mp4', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
|
||||||
|
def _handle_720p_download(self, chosen_caption=None):
|
||||||
|
if self.stream.get_by_itag(698):
|
||||||
|
merge_audio_video(self.title, '720p', 'mp4', download_nonprogressive(self.stream, 698, 140, 'mp4', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
elif self.stream.get_by_itag(298):
|
||||||
|
merge_audio_video(self.title, '720p', 'mp4', download_nonprogressive(self.stream, 298, 140, 'mp4', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
elif self.stream.get_by_itag(136):
|
||||||
|
merge_audio_video(self.title, '720p', 'mp4', download_nonprogressive(self.stream, 136, 140, 'mp4', self.temp_dir), self.captions, chosen_caption)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description=f'PytubePP (Pytube Post Processor) v{version} - A Simple CLI Tool to Download Your Favorite YouTube Videos Effortlessly!')
|
downloader = YouTubeDownloader()
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description=f'PytubePP (Pytube Post Processor) v{downloader.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, 4320p, 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('-dc', '--default-caption', default=argparse.SUPPRESS, help='set default caption (default: none) [available arguments: all language codes, none]')
|
||||||
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('-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('-c', '--caption', default=argparse.SUPPRESS, help='choose caption to embed for the current video (default: your chosen --default-caption) [available arguments: all language codes, none]')
|
||||||
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('-ls', '--list-stream', action='store_true', help='list all available streams (video, audio, caption) (only for debuging purposes)')
|
||||||
parser.add_argument('-ri', '--raw-info', action='store_true', help='show video info in raw json format')
|
parser.add_argument('-ri', '--raw-info', action='store_true', help='show video info in raw json format')
|
||||||
parser.add_argument('-jp', '--json-prettify', action='store_true', help='show json in prettified indented view')
|
parser.add_argument('-jp', '--json-prettify', action='store_true', help='show json in prettified indented view')
|
||||||
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)')
|
||||||
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('-ct', '--clear-temp', action='store_true', help='clear temporary files (audio, video, thumbnail files of the failed, incomplete downloads)')
|
||||||
|
parser.add_argument('-pi', '--postinstall', action='store_true', help='auto install external dependencies (supported os: windows, linux - debian fedora arch, macos)')
|
||||||
parser.add_argument('-v', '--version', action='store_true', help='show version number')
|
parser.add_argument('-v', '--version', action='store_true', help='show version number')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@@ -32,62 +409,178 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if args.url:
|
if args.url:
|
||||||
if 'download_folder' in args:
|
if not is_valid_url(args.url):
|
||||||
|
print('\nInvalid video link! Please enter a valid video url...!!')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# Handle warning messages for ignored flags
|
||||||
|
if hasattr(args, 'download_folder'):
|
||||||
print('\nVideo url supplied! igonering -df flag...!!')
|
print('\nVideo url supplied! igonering -df flag...!!')
|
||||||
|
if hasattr(args, 'default_stream'):
|
||||||
if 'default_stream' in args:
|
|
||||||
print('\nVideo url supplied! ignoreing -ds flag...!!')
|
print('\nVideo url supplied! ignoreing -ds flag...!!')
|
||||||
|
if hasattr(args, 'default_caption'):
|
||||||
|
print('\nVideo url supplied! ignoreing -dc flag...!!')
|
||||||
if args.reset_default:
|
if args.reset_default:
|
||||||
print('\nVideo url supplied! ignoreing -r flag...!!')
|
print('\nVideo url supplied! ignoreing -r flag...!!')
|
||||||
|
|
||||||
if args.clear_temp:
|
if args.clear_temp:
|
||||||
print('\nVideo url supplied! ignoreing -ct flag...!!')
|
print('\nVideo url supplied! ignoreing -ct flag...!!')
|
||||||
|
|
||||||
if args.show_config:
|
if args.show_config:
|
||||||
print('\nVideo url supplied! ignoreing -sc flag...!!')
|
print('\nVideo url supplied! ignoreing -sc flag...!!')
|
||||||
|
if args.postinstall:
|
||||||
|
print('\nVideo url supplied! ignoreing -pi flag...!!')
|
||||||
|
|
||||||
|
# Handle info display flags
|
||||||
if args.show_info:
|
if args.show_info:
|
||||||
show_video_info(args.url)
|
print('Loading...')
|
||||||
|
downloader.show_video_info(args.url)
|
||||||
|
if args.list_stream:
|
||||||
|
print('Loading...')
|
||||||
|
downloader.show_all_streams(args.url)
|
||||||
if args.raw_info:
|
if args.raw_info:
|
||||||
if args.json_prettify:
|
downloader.show_raw_info(args.url, args.json_prettify)
|
||||||
show_raw_info(args.url, True)
|
|
||||||
else:
|
|
||||||
show_raw_info(args.url)
|
|
||||||
|
|
||||||
if args.json_prettify and not args.raw_info:
|
if args.json_prettify and not args.raw_info:
|
||||||
print('\nMissing flag! -jp flag must be used with a flag which returns json data...!! (eg: -ri)')
|
print('\nMissing flag! -jp flag must be used with a flag which returns json data...!! (eg: -ri)')
|
||||||
|
|
||||||
if 'stream' in args:
|
# Handle download cases
|
||||||
download_stream(args.url, args.stream)
|
if hasattr(args, 'stream') and hasattr(args, 'caption'):
|
||||||
|
print('Loading...')
|
||||||
if 'stream' not in args and not args.show_info and not args.raw_info and not args.json_prettify:
|
if downloader.set_video_info(args.url):
|
||||||
if set_global_video_info(args.url):
|
if (args.caption not in downloader.captions.keys()) and (args.caption != 'none'):
|
||||||
if defaultStream == 'max' and maxres != None:
|
print('\nInvalid caption code or caption not available! Please choose a different caption...!! (use -i to see available captions)')
|
||||||
download_stream(args.url, maxres)
|
sys.exit()
|
||||||
return
|
elif args.caption == 'none':
|
||||||
if (defaultStream == 'mp3' and stream.get_by_itag(140)) or (defaultStream != 'max' and stream.filter(res=defaultStream)):
|
downloader.download_stream(args.url, args.stream)
|
||||||
download_stream(args.url, defaultStream)
|
elif args.stream == 'mp3' and downloader.stream.get_by_itag(140):
|
||||||
else:
|
print(f'\nYou have chosen to download mp3 stream! ( Captioning audio files is not supported )')
|
||||||
if maxres != None:
|
answer = input('Do you still want to continue downloading? [yes/No]: ').strip().lower()
|
||||||
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']:
|
if answer in ['yes', 'y']:
|
||||||
download_stream(args.url, maxres)
|
downloader.download_stream(args.url, args.stream)
|
||||||
|
else:
|
||||||
|
print('Download cancelled! exiting...!!')
|
||||||
|
else:
|
||||||
|
downloader.download_stream(args.url, args.stream, args.caption)
|
||||||
|
elif hasattr(args, 'stream'):
|
||||||
|
print('Loading...')
|
||||||
|
if downloader.set_video_info(args.url):
|
||||||
|
if downloader.default_caption == 'none':
|
||||||
|
downloader.download_stream(args.url, args.stream)
|
||||||
|
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 )')
|
||||||
|
answer = input('Do you still want to continue downloading? [yes/No]: ').strip().lower()
|
||||||
|
if answer in ['yes', 'y']:
|
||||||
|
downloader.download_stream(args.url, args.stream)
|
||||||
|
else:
|
||||||
|
print('Download cancelled! exiting...!!')
|
||||||
|
elif downloader.default_caption in downloader.captions.keys():
|
||||||
|
downloader.download_stream(args.url, args.stream, downloader.default_caption)
|
||||||
|
else:
|
||||||
|
print(f'\nDefault caption not available! ( Default: {downloader.default_caption} | Available: {[caption.code for caption in downloader.captions.keys()] or "Nothing"} )')
|
||||||
|
answer = input('Do you still want to continue downloading without caption? [yes/No]: ').strip().lower()
|
||||||
|
if answer in ['yes', 'y']:
|
||||||
|
downloader.download_stream(args.url, args.stream)
|
||||||
|
else:
|
||||||
|
print('Download cancelled! exiting...!!')
|
||||||
|
elif hasattr(args, 'caption'):
|
||||||
|
print('Loading...')
|
||||||
|
if downloader.set_video_info(args.url):
|
||||||
|
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)')
|
||||||
|
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]: ').strip().lower()
|
||||||
|
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:
|
||||||
|
downloader.download_stream(args.url, downloader.maxres, args.caption)
|
||||||
|
elif downloader.default_stream == 'mp3' and downloader.stream.get_by_itag(140):
|
||||||
|
print(f'\nDefault stream set to mp3! ( Captioning audio files is not supported )')
|
||||||
|
answer = input('Do you still want to continue downloading? [yes/No]: ').strip().lower()
|
||||||
|
if answer in ['yes', 'y']:
|
||||||
|
downloader.download_stream(args.url, downloader.default_stream)
|
||||||
|
else:
|
||||||
|
print('Download cancelled! exiting...!!')
|
||||||
|
elif downloader.default_stream != 'max' and downloader.stream.filter(res=downloader.default_stream):
|
||||||
|
downloader.download_stream(args.url, downloader.default_stream, args.caption)
|
||||||
|
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]: ').strip().lower()
|
||||||
|
if answer in ['yes', 'y']:
|
||||||
|
downloader.download_stream(args.url, downloader.maxres, args.caption)
|
||||||
|
else:
|
||||||
|
print('Download cancelled! exiting...!!')
|
||||||
|
else:
|
||||||
|
print('Sorry, No downloadable video stream found....!!!')
|
||||||
|
elif not any([args.show_info, args.raw_info, args.json_prettify, args.list_stream]): # If no info flags are set
|
||||||
|
print('Loading...')
|
||||||
|
if downloader.set_video_info(args.url):
|
||||||
|
if downloader.default_stream == 'max' and downloader.maxres:
|
||||||
|
if downloader.default_caption == 'none':
|
||||||
|
downloader.download_stream(args.url, downloader.maxres)
|
||||||
|
elif downloader.default_caption in downloader.captions.keys():
|
||||||
|
downloader.download_stream(args.url, downloader.maxres, downloader.default_caption)
|
||||||
|
else:
|
||||||
|
print(f'\nDefault caption not available! ( Default: {downloader.default_caption} | Available: {[caption.code for caption in downloader.captions.keys()] or "Nothing"} )')
|
||||||
|
answer = input('Do you still want to continue downloading without caption? [yes/No]: ').strip().lower()
|
||||||
|
if answer in ['yes', 'y']:
|
||||||
|
downloader.download_stream(args.url, downloader.maxres)
|
||||||
|
else:
|
||||||
|
print('Download cancelled! exiting...!!')
|
||||||
|
elif (downloader.default_stream == 'mp3' and downloader.stream.get_by_itag(140)) or (downloader.default_stream != 'max' and downloader.stream.filter(res=downloader.default_stream)):
|
||||||
|
if downloader.default_caption == 'none':
|
||||||
|
downloader.download_stream(args.url, downloader.default_stream)
|
||||||
|
elif downloader.default_stream == 'mp3' and downloader.stream.get_by_itag(140):
|
||||||
|
print(f'\nDefault stream set to mp3! ( Captioning audio files is not supported )')
|
||||||
|
answer = input('Do you still want to continue downloading? [yes/No]: ').strip().lower()
|
||||||
|
if answer in ['yes', 'y']:
|
||||||
|
downloader.download_stream(args.url, downloader.default_stream)
|
||||||
|
else:
|
||||||
|
print('Download cancelled! exiting...!!')
|
||||||
|
elif downloader.default_caption in downloader.captions.keys():
|
||||||
|
downloader.download_stream(args.url, downloader.default_stream, downloader.default_caption)
|
||||||
|
else:
|
||||||
|
print(f'\nDefault caption not available! ( Default: {downloader.default_caption} | Available: {[caption.code for caption in downloader.captions.keys()] or "Nothing"} )')
|
||||||
|
answer = input('Do you still want to continue downloading without caption? [yes/No]: ').strip().lower()
|
||||||
|
if answer in ['yes', 'y']:
|
||||||
|
downloader.download_stream(args.url, downloader.default_stream)
|
||||||
|
else:
|
||||||
|
print('Download cancelled! exiting...!!')
|
||||||
|
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]: ').strip().lower()
|
||||||
|
if answer in ['yes', 'y']:
|
||||||
|
if downloader.default_caption == 'none':
|
||||||
|
downloader.download_stream(args.url, downloader.maxres)
|
||||||
|
elif downloader.default_caption in downloader.captions.keys():
|
||||||
|
downloader.download_stream(args.url, downloader.maxres, downloader.default_caption)
|
||||||
|
else:
|
||||||
|
print(f'\nDefault caption not available! ( Default: {downloader.default_caption} | Available: {[caption.code for caption in downloader.captions.keys()] or "Nothing"} )')
|
||||||
|
answer = input('Do you still want to continue downloading without caption? [yes/No]: ').strip().lower()
|
||||||
|
if answer in ['yes', 'y']:
|
||||||
|
downloader.download_stream(args.url, downloader.maxres)
|
||||||
|
else:
|
||||||
|
print('Download cancelled! exiting...!!')
|
||||||
else:
|
else:
|
||||||
print('Download cancelled! exiting...!!')
|
print('Download cancelled! exiting...!!')
|
||||||
else:
|
else:
|
||||||
print('Sorry, No downloadable video stream found....!!!')
|
print('Sorry, No downloadable video stream found....!!!')
|
||||||
else:
|
else:
|
||||||
print('\nInvalid video link! Please enter a valid video url...!!')
|
if hasattr(args, 'download_folder'):
|
||||||
|
if args.download_folder != downloader.download_dir:
|
||||||
else:
|
|
||||||
if 'download_folder' in args:
|
|
||||||
if args.download_folder != downloadDIR:
|
|
||||||
if os.path.isdir(args.download_folder):
|
if os.path.isdir(args.download_folder):
|
||||||
update_config('downloadDIR', args.download_folder)
|
update_config('downloadDIR', args.download_folder)
|
||||||
os.makedirs(args.download_folder, exist_ok=True)
|
os.makedirs(args.download_folder, exist_ok=True)
|
||||||
@@ -97,8 +590,8 @@ def main():
|
|||||||
else:
|
else:
|
||||||
print('\nDownload folder path is the same! Not updating...!!')
|
print('\nDownload folder path is the same! Not updating...!!')
|
||||||
|
|
||||||
if 'default_stream' in args:
|
if hasattr(args, 'default_stream'):
|
||||||
if args.default_stream != defaultStream:
|
if args.default_stream != downloader.default_stream:
|
||||||
if args.default_stream in ['144p', '240p', '360p', '480p', '720p', '1080p', '1440p', '2160p', '4320p', '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}')
|
||||||
@@ -107,6 +600,21 @@ def main():
|
|||||||
else:
|
else:
|
||||||
print('\nDefault stream is the same! Not updating...!!')
|
print('\nDefault stream is the same! Not updating...!!')
|
||||||
|
|
||||||
|
if hasattr(args, 'default_caption'):
|
||||||
|
if args.default_caption != downloader.default_caption:
|
||||||
|
if not (re.match(r'^[a-z]{2}(-[A-Za-z]+)?$', args.default_caption) or
|
||||||
|
re.match(r'^a\.[a-z]{2}(-[A-Za-z]+)?$', args.default_caption) or
|
||||||
|
re.match(r'^none$', args.default_caption)):
|
||||||
|
print('\nInvalid caption code! Allowed formats are:\n'
|
||||||
|
'- ISO 639-1 language codes (e.g: en, zh-Hans)\n'
|
||||||
|
'- Auto-generated variants: a.ISO639-1LanguageCode (e.g: a.en, a.zh-Hans)\n'
|
||||||
|
'- none\n')
|
||||||
|
else:
|
||||||
|
update_config('defaultCaption', args.default_caption)
|
||||||
|
print(f'\nDefault caption updated to: {args.default_caption}')
|
||||||
|
else:
|
||||||
|
print('\nDefault caption is the same! Not updating...!!')
|
||||||
|
|
||||||
if args.reset_default:
|
if args.reset_default:
|
||||||
reset_config()
|
reset_config()
|
||||||
|
|
||||||
@@ -114,23 +622,28 @@ def main():
|
|||||||
clear_temp_files()
|
clear_temp_files()
|
||||||
|
|
||||||
if args.show_config:
|
if args.show_config:
|
||||||
print(f'\ndownloadDIR: {downloadDIR}\ntempDIR: {tempDIR}\nconfigDIR: {configDIR}\ndefaultStream: {defaultStream}\n')
|
print(f'\ntempDIR: {downloader.temp_dir} (Unchangeable) \nconfigDIR: {downloader.config_dir} (Unchangeable)\ndownloadDIR: {downloader.download_dir}\ndefaultStream: {downloader.default_stream}\ndefaultCaption: {downloader.default_caption}\n')
|
||||||
|
|
||||||
|
if args.postinstall:
|
||||||
|
postinstall()
|
||||||
|
|
||||||
if args.version:
|
if args.version:
|
||||||
print(f'pytubepp {version}')
|
print(f'pytubepp {downloader.version}')
|
||||||
|
|
||||||
if args.show_info:
|
if args.show_info:
|
||||||
print('\nNo video url supplied! exiting...!!')
|
print('\nNo video url supplied! exiting...!!')
|
||||||
|
|
||||||
|
if args.list_stream:
|
||||||
|
print('\nNo video url supplied! exiting...!!')
|
||||||
|
|
||||||
if args.raw_info:
|
if args.raw_info:
|
||||||
print('\nNo video url supplied! exiting...!!')
|
print('\nNo video url supplied! exiting...!!')
|
||||||
|
|
||||||
if args.json_prettify and not args.raw_info:
|
if args.json_prettify and not args.raw_info:
|
||||||
print('\nMissing flag! -jp flag must be used with a flag which returns json data...!! (eg: -ri)')
|
print('\nMissing flag! -jp flag must be used with a flag which returns json data...!! (eg: -ri)')
|
||||||
|
|
||||||
if 'stream' in args:
|
if hasattr(args, 'stream'):
|
||||||
print('\nNo video url supplied! exiting...!!')
|
print('\nNo video url supplied! exiting...!!')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
130
pytubepp/postinstaller.py
Normal file
130
pytubepp/postinstaller.py
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
from .utils import ffmpeg_installed, nodejs_installed
|
||||||
|
import subprocess, platform
|
||||||
|
|
||||||
|
def postinstall():
|
||||||
|
os_type = platform.system().lower()
|
||||||
|
package_manager = None
|
||||||
|
|
||||||
|
print("### PytubePP Post-Install Script ###\n")
|
||||||
|
|
||||||
|
print("Checking requirements...")
|
||||||
|
ffmpeg_needed = not ffmpeg_installed()
|
||||||
|
nodejs_needed = not nodejs_installed()
|
||||||
|
|
||||||
|
if ffmpeg_needed or nodejs_needed:
|
||||||
|
if os_type == 'windows':
|
||||||
|
version_info = platform.version().split('.')
|
||||||
|
if int(version_info[0]) >= 10 and (int(version_info[1]) > 0 or int(version_info[2]) >= 1709):
|
||||||
|
winget_check = subprocess.run(['winget', '--version'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
if winget_check.returncode == 0:
|
||||||
|
print("OS: Windows (winget)")
|
||||||
|
package_manager = 'winget' # Windows Package Manager
|
||||||
|
else:
|
||||||
|
print("OS: Windows (winget not enabled)")
|
||||||
|
user_input = input("WinGet is not available. Do you want to enable winget? (Make sure to login to Windows before enabling) [Yes/no]: ").strip().lower()
|
||||||
|
if user_input in ['yes', 'y', '']:
|
||||||
|
print("Enabling winget...")
|
||||||
|
subprocess.run(['powershell', '-Command', 'Add-AppxPackage -RegisterByFamilyName -MainPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe'])
|
||||||
|
print("WinGet enabled successfully! Please restart your computer and re-run the post install script: pytubepp --postinstall")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print("Installation aborted! exiting...!!")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print("OS: Windows (winget not supported)")
|
||||||
|
print("Unsupported Windows version! WinGet requires Windows 10 1709 (build 16299) or later, Please install dependencies manually...!!")
|
||||||
|
return
|
||||||
|
elif os_type == 'linux':
|
||||||
|
# Determine the Linux distribution
|
||||||
|
if subprocess.run(['command', '-v', 'apt'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0:
|
||||||
|
print("OS: Linux (apt)")
|
||||||
|
package_manager = 'apt' # APT for Debian/Ubuntu
|
||||||
|
elif subprocess.run(['command', '-v', 'dnf'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0:
|
||||||
|
print("OS: Linux (dnf)")
|
||||||
|
distro_id = subprocess.run(['grep', '^ID=', '/etc/os-release'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||||
|
if distro_id.returncode == 0 and 'fedora' in distro_id.stdout.decode().strip() and ffmpeg_needed:
|
||||||
|
user_input = input("Looks like you are using Fedora. Do you want to enable RPM Fusion free and nonfree repositories? (answer no if already enabled) [Yes/no]: ").strip().lower()
|
||||||
|
if user_input in ['yes', 'y', '']:
|
||||||
|
print("Enabling RPM Fusion repositories...")
|
||||||
|
fedora_version = subprocess.run(['rpm', '-E', '%fedora'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||||
|
if fedora_version.returncode == 0:
|
||||||
|
fedora_version_str = fedora_version.stdout.decode().strip()
|
||||||
|
subprocess.run(['sudo', 'dnf', 'install', f'https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-{fedora_version_str}.noarch.rpm', '-y'], check=True)
|
||||||
|
subprocess.run(['sudo', 'dnf', 'install', f'https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-{fedora_version_str}.noarch.rpm', '-y'], check=True)
|
||||||
|
else:
|
||||||
|
print("Failed to retrieve Fedora version. Please install RPM Fusion repositories manually.")
|
||||||
|
else:
|
||||||
|
print("RPM Fusion repositories installation skipped...!!")
|
||||||
|
package_manager = 'dnf' # DNF for Fedora
|
||||||
|
elif subprocess.run(['command', '-v', 'pacman'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0:
|
||||||
|
print("OS: Linux (pacman)")
|
||||||
|
package_manager = 'pacman' # Pacman for Arch Linux
|
||||||
|
else:
|
||||||
|
print("OS: Linux (unknown)")
|
||||||
|
print("Unsupported Linux distribution! Please install dependencies manually...!!")
|
||||||
|
return
|
||||||
|
elif os_type == 'darwin':
|
||||||
|
homebrew_check = subprocess.run(['brew', '--version'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
if homebrew_check.returncode == 0:
|
||||||
|
print("OS: MacOS (brew)")
|
||||||
|
package_manager = 'brew' # Homebrew for macOS
|
||||||
|
else:
|
||||||
|
print("OS: MacOS (brew not installed)")
|
||||||
|
user_input = input("Homebrew is not installed. Do you want to install Homebrew? [Yes/no]: ").strip().lower()
|
||||||
|
if user_input in ['yes', 'y', '']:
|
||||||
|
print("Installing Homebrew...")
|
||||||
|
subprocess.run('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"', shell=True)
|
||||||
|
print("Homebrew installation completed! Please restart your mac and re-run the post install script: pytubepp --postinstall")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print("Installation aborted! exiting...!!")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print("Unsupported OS! Please install dependencies manually...!!")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("The following packages are about to be installed:")
|
||||||
|
if ffmpeg_needed:
|
||||||
|
print("- FFmpeg")
|
||||||
|
if nodejs_needed:
|
||||||
|
print("- Node.js")
|
||||||
|
|
||||||
|
user_input = input("Do you want to proceed with the installation? [Yes/no]: ").strip().lower()
|
||||||
|
if user_input not in ['yes', 'y', '']:
|
||||||
|
print("Installation aborted! exiting...!!")
|
||||||
|
return
|
||||||
|
|
||||||
|
if ffmpeg_needed:
|
||||||
|
print("Installing FFmpeg...")
|
||||||
|
install_ffmpeg(package_manager)
|
||||||
|
if nodejs_needed:
|
||||||
|
print("Installing Node.js...")
|
||||||
|
install_nodejs(package_manager)
|
||||||
|
else:
|
||||||
|
print("Dependencies already satisfied! exiting...!!")
|
||||||
|
|
||||||
|
def install_ffmpeg(package_manager):
|
||||||
|
if package_manager == 'winget':
|
||||||
|
subprocess.run(['winget', 'install', 'ffmpeg'], check=True)
|
||||||
|
elif package_manager == 'apt':
|
||||||
|
subprocess.run(['sudo', 'apt', 'install', 'ffmpeg', '-y'], check=True)
|
||||||
|
elif package_manager == 'dnf':
|
||||||
|
subprocess.run(['sudo', 'dnf', 'install', 'ffmpeg', '-y'], check=True)
|
||||||
|
elif package_manager == 'pacman':
|
||||||
|
subprocess.run(['sudo', 'pacman', '-S', 'ffmpeg', '--noconfirm'], check=True)
|
||||||
|
elif package_manager == 'brew':
|
||||||
|
subprocess.run(['brew', 'install', 'ffmpeg'], check=True)
|
||||||
|
print("FFmpeg installation completed")
|
||||||
|
|
||||||
|
def install_nodejs(package_manager):
|
||||||
|
if package_manager == 'winget':
|
||||||
|
subprocess.run(['winget', 'install', 'OpenJS.NodeJS.LTS'], check=True)
|
||||||
|
elif package_manager == 'apt':
|
||||||
|
subprocess.run(['sudo', 'apt', 'install', 'nodejs', '-y'], check=True)
|
||||||
|
elif package_manager == 'dnf':
|
||||||
|
subprocess.run(['sudo', 'dnf', 'install', 'nodejs', '-y'], check=True)
|
||||||
|
elif package_manager == 'pacman':
|
||||||
|
subprocess.run(['sudo', 'pacman', '-S', 'nodejs-lts-iron', 'npm', '--noconfirm'], check=True)
|
||||||
|
elif package_manager == 'brew':
|
||||||
|
subprocess.run(['brew', 'install', 'node'], check=True)
|
||||||
|
print("Node.js installation completed")
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB
|
from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB
|
||||||
from .config import get_temporary_directory, load_config
|
from .config import get_temporary_directory, load_config
|
||||||
from .utils import get_unique_filename, postprocess_cleanup
|
from .utils import get_unique_filename, postprocess_cleanup, unpack_caption
|
||||||
from .download import download_thumbnail
|
from .download import download_thumbnail
|
||||||
import os, shutil, ffmpy
|
import os, shutil, ffmpy
|
||||||
|
|
||||||
@@ -8,12 +8,51 @@ userConfig = load_config()
|
|||||||
downloadDIR = userConfig['downloadDIR']
|
downloadDIR = userConfig['downloadDIR']
|
||||||
tempDIR = get_temporary_directory()
|
tempDIR = get_temporary_directory()
|
||||||
|
|
||||||
def merge_audio_video(title, resolution, file_extention, random_filename, tempDIR=tempDIR, downloadDIR=downloadDIR):
|
def merge_audio_video(title, resolution, file_extention, random_filename, captions, caption_code=None, tempDIR=tempDIR, downloadDIR=downloadDIR):
|
||||||
video_file = os.path.join(tempDIR, random_filename + '_vdo.' + file_extention)
|
video_file = os.path.join(tempDIR, random_filename + '_vdo.' + file_extention)
|
||||||
audio_file = os.path.join(tempDIR, random_filename + '_ado.' + 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_temp_file = os.path.join(tempDIR, random_filename + '_merged.' + file_extention)
|
||||||
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)) if not caption_code else os.path.join(downloadDIR, get_unique_filename(title + '_' + resolution + '_' + caption_code + '.' + file_extention))
|
||||||
|
|
||||||
|
if caption_code:
|
||||||
|
print(f'Downloading Caption ({caption_code})...')
|
||||||
|
caption = captions[caption_code]
|
||||||
|
_, caption_lang = unpack_caption(caption)
|
||||||
|
srt_file = os.path.join(tempDIR, random_filename + '_cap.srt')
|
||||||
|
caption.save_captions(srt_file)
|
||||||
|
vtt_file = os.path.join(tempDIR, random_filename + '_cap.vtt')
|
||||||
|
|
||||||
|
print('Processing...')
|
||||||
|
if file_extention == 'webm':
|
||||||
|
devnull = open(os.devnull, 'w')
|
||||||
|
ff_convert = ffmpy.FFmpeg(
|
||||||
|
inputs={srt_file: None},
|
||||||
|
outputs={vtt_file: None}
|
||||||
|
)
|
||||||
|
ff_convert.run(stdout=devnull, stderr=devnull)
|
||||||
|
subtitle_file = vtt_file
|
||||||
|
subtitle_codec = 'webvtt'
|
||||||
|
else:
|
||||||
|
subtitle_file = srt_file
|
||||||
|
subtitle_codec = 'mov_text'
|
||||||
|
|
||||||
|
input_params = {video_file: None, audio_file: None}
|
||||||
|
output_params = {output_temp_file: ['-i', subtitle_file, '-c:v', 'copy', '-c:a', 'copy',
|
||||||
|
'-c:s', subtitle_codec, '-metadata:s:s:0', f'language={caption_code}',
|
||||||
|
'-metadata:s:s:0', f'title={caption_lang}', '-metadata:s:s:0', f'handler_name={caption_lang}']}
|
||||||
|
|
||||||
|
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)
|
||||||
|
cleanup_files = ['_vdo.' + file_extention, '_ado.' + file_extention, '_cap.srt', '_merged.' + file_extention]
|
||||||
|
if file_extention == 'webm':
|
||||||
|
cleanup_files.append('_cap.vtt')
|
||||||
|
postprocess_cleanup(tempDIR, cleanup_files, random_filename)
|
||||||
|
print('Done! 🎉')
|
||||||
|
else:
|
||||||
input_params = {video_file: None, audio_file: None}
|
input_params = {video_file: None, audio_file: None}
|
||||||
output_params = {output_temp_file: ['-c:v', 'copy', '-c:a', 'copy']}
|
output_params = {output_temp_file: ['-c:v', 'copy', '-c:a', 'copy']}
|
||||||
|
|
||||||
|
|||||||
@@ -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')
|
||||||
@@ -22,7 +36,7 @@ def get_version():
|
|||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
|
||||||
def is_valid_url(url):
|
def is_valid_url(url):
|
||||||
match = re.search(r"(https?://(?:www\.|music\.)?youtube\.com/watch\?v=[^&]{11}|https?://youtu\.be/[^?&]*(\?si=[^&]*)?)", url)
|
match = re.search(r"(https?://(?:www\.|music\.)?youtube\.com/(?:watch\?v=[^&]{11}|shorts/[^?&]+)|https?://youtu\.be/[^?&]*(\?si=[^&]*)?)", url)
|
||||||
return match
|
return match
|
||||||
|
|
||||||
def get_unique_filename(filename, directory=downloadDIR):
|
def get_unique_filename(filename, directory=downloadDIR):
|
||||||
@@ -33,6 +47,17 @@ def get_unique_filename(filename, directory=downloadDIR):
|
|||||||
counter += 1
|
counter += 1
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
def unpack_caption(caption):
|
||||||
|
caption_str = str(caption)
|
||||||
|
code_start = caption_str.find('code="') + 6
|
||||||
|
code_end = caption_str.find('"', code_start)
|
||||||
|
lang_start = caption_str.find('lang="') + 6
|
||||||
|
lang_end = caption_str.find('"', lang_start)
|
||||||
|
|
||||||
|
code = caption_str[code_start:code_end]
|
||||||
|
lang = caption_str[lang_start:lang_end]
|
||||||
|
return code, lang
|
||||||
|
|
||||||
def postprocess_cleanup(dir, files, random_filename):
|
def postprocess_cleanup(dir, files, random_filename):
|
||||||
for file in files:
|
for file in files:
|
||||||
file_path = os.path.join(dir, random_filename + file)
|
file_path = os.path.join(dir, random_filename + file)
|
||||||
|
|||||||
@@ -1,509 +0,0 @@
|
|||||||
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...!!')
|
|
||||||
Reference in New Issue
Block a user