From f3cf67bda793769179688849d3de2d19836ba59b Mon Sep 17 00:00:00 2001 From: Subhamoy Biswas Date: Fri, 24 Jan 2025 21:27:33 +0530 Subject: [PATCH] (feat): added post installation script -pi and improved docs --- README.md | 40 +++++++----- pytubepp/main.py | 11 +++- pytubepp/postinstaller.py | 130 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 17 deletions(-) create mode 100644 pytubepp/postinstaller.py diff --git a/README.md b/README.md index 34fa496..26fdcd3 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,12 @@ > **🥰 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 (10 / 11) +- Linux (Debian, Fedora, Arch) +- MacOS +- Android (Termux) + ### **🏷️ Features** * Auto Post-Process & Merge YouTube DASH Streams * Supports upto 8K 60fps HDR Stream Download @@ -44,6 +50,9 @@ - Windows (10/11): `winget install Python.Python.3.13`
- MacOS (using Homebrew): `brew 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 - Linux (Debian): `sudo apt install ffmpeg`
- 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`
@@ -68,7 +77,7 @@ 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)** ``` pip install pytubefix pytubepp --upgrade @@ -108,20 +117,21 @@ pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -i * To cancel/stop an ongoing download press `CTRL` + `C` on keyboard (it is recommended to run the `-ct` flag once after canceling an ongoing download). * List of all available flags are given below: -| 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 | -| -c | 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 | -| -i | Shows the video information like: Title, Author, Views, Publication Date, Duration, Available Download Streams and Captions | NO | YES | No parameters | No default | -| -ls | Lists all available streams (video, audio, caption) (only for debuging purposes) | NO | YES | No parameters | No default | -| -ri | Shows the video information in raw json format | NO | YES | No parameters | No default | -| -jp | Shows raw json output in prettified view (with indentation: 4) (primarily used with -ri flag)| 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` | -| -dc | 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` | -| -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 | -| -r | Reset to default configuration (Download Folder, Default Stream) | NO | NO | No parameters | No default | -| -sc | Show all current user configurations | NO | NO | No parameters | No default | -| -ct | Clear temporary files (audio, video, thumbnail) of the failed, incomplete downloads | NO | NO | No parameters | No default | +| Short Flag | Flag | Usage | Requires Parameter | Requires URL | Parameters | Default | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| -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 | +| -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 | +| -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 | +| -ls | --list-stream | Lists all available streams (video, audio, caption) (only for debuging purposes) | NO | YES | No parameters | No default | +| -ri | --raw-info | Shows the video information in raw json format | NO | YES | No parameters | No default | +| -jp | --json-prettify | Shows raw json output in prettified view (with indentation: 4) (primarily used with -ri flag)| NO | YES | 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` | +| -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` | +| -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) | 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) of the failed, incomplete downloads | NO | NO | No parameters | No default | +| -pi | --postinstall | Auto install all external dependencies (FFmpeg, Node.js) (in Windows, Linux - debian fedora arch, MacOS) | NO | NO | No parameters | No default | ### 🛠️ Contributing / Building from Source diff --git a/pytubepp/main.py b/pytubepp/main.py index 561cb3a..60f1413 100644 --- a/pytubepp/main.py +++ b/pytubepp/main.py @@ -4,6 +4,7 @@ from .config import get_temporary_directory, load_config, update_config, reset_c from .download import download_progressive, download_nonprogressive, download_audio, progress from .postprocess import merge_audio_video, convert_to_mp3 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 class YouTubeDownloader: @@ -46,7 +47,7 @@ class YouTubeDownloader: 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, read https://github.com/neosubhamoy/pytubepp#%EF%B8%8F-installation for instructions\n") + 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) @@ -295,7 +296,7 @@ class YouTubeDownloader: if not ffmpeg_installed(): print("\nWarning: FFmpeg is not installed or not found in PATH!") print("Some core functionalities like video processing will not work properly without FFmpeg") - print("Please install FFmpeg, read https://github.com/neosubhamoy/pytubepp#%EF%B8%8F-installation for instructions\n") + 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): @@ -388,6 +389,7 @@ def main(): parser.add_argument('-sc', '--show-config', action='store_true', help='show all current user config settings') parser.add_argument('-r', '--reset-default', action='store_true', help='reset to default settings (download_folder and default_stream)') parser.add_argument('-ct', '--clear-temp', action='store_true', help='clear temporary files (audio, video, thumbnail files of the failed, incomplete downloads)') + parser.add_argument('-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') args = parser.parse_args() @@ -414,6 +416,8 @@ def main(): print('\nVideo url supplied! ignoreing -ct flag...!!') if args.show_config: 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: @@ -641,6 +645,9 @@ def main(): if args.show_config: 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: print(f'pytubepp {downloader.version}') diff --git a/pytubepp/postinstaller.py b/pytubepp/postinstaller.py new file mode 100644 index 0000000..ea62314 --- /dev/null +++ b/pytubepp/postinstaller.py @@ -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") \ No newline at end of file