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

6 Commits

7 changed files with 108 additions and 27 deletions

View File

@@ -1,7 +1,7 @@
on: on:
push: push:
branches: tags:
- main - 'v*.*.*-*'
name: 🚀 Publish to PyPI name: 🚀 Publish to PyPI
jobs: jobs:

43
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
on:
push:
tags:
- 'v*.*.*-*'
name: 🚀 Release on GitHub
jobs:
release:
name: 🎉 Release on GitHub
runs-on: ubuntu-latest
steps:
- name: 🚚 Checkout repository
uses: actions/checkout@v4
- name: 🐍 Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: 📦 Install dependencies
run: |
pip install -r requirements.txt
- name: 🛠️ Build package
run: python3 -m build
- name: "✏️ Generate release changelog"
id: gen-changelog
uses: janheinrichmerker/action-github-changelog-generator@v2.3
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: 🚀 Publish release to GitHub
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
name: ${{github.event.repository.name}}-${{github.ref_name}}
body: ${{ steps.gen-changelog.outputs.changelog }}
files: dist/*
draft: false
prerelease: false
make_latest: true

View File

@@ -33,14 +33,15 @@
* [Node.js](https://nodejs.org/en/download/) * [Node.js](https://nodejs.org/en/download/)
### **🧩 Python Dependencies** ### **🧩 Python Dependencies**
* [pytubefix](https://pypi.org/project/pytubefix/) * [pytubefix](https://pypi.org/project/pytubefix/),
* [ffmpy](https://pypi.org/project/ffmpy/) [ffmpy](https://pypi.org/project/ffmpy/),
* [mutagen](https://pypi.org/project/mutagen/) [mutagen](https://pypi.org/project/mutagen/),
* [tabulate](https://pypi.org/project/tabulate/) [tabulate](https://pypi.org/project/tabulate/),
* [tqdm](https://pypi.org/project/tqdm/) [tqdm](https://pypi.org/project/tqdm/),
* [appdirs](https://pypi.org/project/appdirs/) [appdirs](https://pypi.org/project/appdirs/),
* [requests](https://pypi.org/project/requests/) [requests](https://pypi.org/project/requests/),
* [setuptools](https://pypi.org/project/setuptools/) [rich](https://pypi.org/project/rich/),
[setuptools](https://pypi.org/project/setuptools/)
### **🛠️ Installation** ### **🛠️ Installation**

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "pytubepp" name = "pytubepp"
version = "1.1.8" version = "1.1.9"
authors = [ authors = [
{ name="Subhamoy Biswas", email="hey@neosubhamoy.com" }, { name="Subhamoy Biswas", email="hey@neosubhamoy.com" },
] ]
@@ -43,6 +43,7 @@ dependencies = [
"tabulate", "tabulate",
"tqdm", "tqdm",
"appdirs", "appdirs",
"rich",
"setuptools", "setuptools",
] ]

View File

@@ -1,9 +1,10 @@
from pytubefix import YouTube from pytubefix import YouTube
from tabulate import tabulate from tabulate import tabulate
from rich import print as rprint
from .config import get_temporary_directory, load_config, update_config, reset_config from .config import get_temporary_directory, load_config, update_config, reset_config
from .download import download_progressive, download_nonprogressive, download_audio, progress from .download import download_progressive, download_nonprogressive, download_audio, progress
from .postprocess import merge_audio_video, convert_to_mp3 from .postprocess import merge_audio_video, convert_to_mp3
from .utils import get_version, clear_temp_files, is_valid_url, network_available, ffmpeg_installed, nodejs_installed, unpack_caption from .utils import get_version, clear_temp_files, is_valid_url, network_available, ffmpeg_installed, nodejs_installed, unpack_caption, check_update
from .postinstaller import postinstall from .postinstaller import postinstall
import appdirs, os, re, sys, argparse, json import appdirs, os, re, sys, argparse, json
@@ -45,13 +46,18 @@ class YouTubeDownloader:
sys.exit() sys.exit()
if not nodejs_installed(): if not nodejs_installed():
print("\nWarning: Node.js is not installed or not found in PATH!") rprint("\n[dark_orange]WARNING:[/dark_orange] Node.js is not installed or not found in PATH!")
print("BotGuard poToken generation will not work properly without Node.js environment") 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") rprint("Please install Node.js, by running: [green]pytubepp --postinstall[/green] or read [steel_blue3]https://github.com/neosubhamoy/pytubepp#%EF%B8%8F-installation[/steel_blue3] for manual instructions\n")
update = check_update()
if update[0]:
rprint(f'\n[blue]NOTE:[/blue] A newer version of pytubepp is available! ([dark_orange]v{update[1]}[/dark_orange] -> [light_green]v{update[2]}[/light_green])')
rprint(f'Please upgrade to the latest version using: [green]{update[3]}[/green]')
if is_valid_url(link): if is_valid_url(link):
link = is_valid_url(link).group(1) link = is_valid_url(link).group(1)
self.video = YouTube(link, 'WEB', on_progress_callback=progress) self.video = YouTube(link, on_progress_callback=progress)
self.author = self.video.author self.author = self.video.author
self.title = re.sub(r'[\\/*?:"<>|]', '_', self.author + ' - ' + self.video.title) self.title = re.sub(r'[\\/*?:"<>|]', '_', self.author + ' - ' + self.video.title)
self.thumbnail = self.video.thumbnail_url self.thumbnail = self.video.thumbnail_url
@@ -159,7 +165,7 @@ class YouTubeDownloader:
print('Sorry, No video streams found....!!!') print('Sorry, No video streams found....!!!')
sys.exit() 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(f'\nTitle: {self.video.title}\nAuthor: {self.author}\nPublished On: {self.video.publish_date.strftime("%d/%m/%Y") if self.video.publish_date else "Unknown"}\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('\n')
print(tabulate(table, headers=['Stream', 'Alias (for -s flag)', 'Format', 'Size', 'FrameRate', 'V-Codec', 'A-Codec', 'V-BitRate', 'A-BitRate'])) print(tabulate(table, headers=['Stream', 'Alias (for -s flag)', 'Format', 'Size', 'FrameRate', 'V-Codec', 'A-Codec', 'V-BitRate', 'A-BitRate']))
@@ -237,7 +243,7 @@ class YouTubeDownloader:
'author': self.author, 'author': self.author,
'thumbnail_url': self.thumbnail, 'thumbnail_url': self.thumbnail,
'views': self.video.views, 'views': self.video.views,
'published_on': self.video.publish_date.strftime('%d/%m/%Y'), 'published_on': self.video.publish_date.strftime('%d/%m/%Y') if self.video.publish_date else None,
'duration': self.video.length, 'duration': self.video.length,
'streams': streams_list, 'streams': streams_list,
'captions': captions_list or None 'captions': captions_list or None
@@ -304,9 +310,9 @@ class YouTubeDownloader:
def download_stream(self, link, chosen_stream, chosen_caption=None): def download_stream(self, link, chosen_stream, chosen_caption=None):
if not ffmpeg_installed(): if not ffmpeg_installed():
print("\nWarning: FFmpeg is not installed or not found in PATH!") rprint("\n[dark_orange]WARNING:[/dark_orange] FFmpeg is not installed or not found in PATH!")
print("Some core functionalities like video processing will not work properly without FFmpeg") 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") rprint("Please install FFmpeg, by running: [green]pytubepp --postinstall[/green] or read [steel_blue3]https://github.com/neosubhamoy/pytubepp#%EF%B8%8F-installation[/steel_blue3] for manual instructions\n")
sys.exit() sys.exit()
if self.set_video_info(link): if self.set_video_info(link):
@@ -431,10 +437,10 @@ def main():
# Handle info display flags # Handle info display flags
if args.show_info: if args.show_info:
print('Loading...') rprint('Loading...')
downloader.show_video_info(args.url) downloader.show_video_info(args.url)
if args.list_stream: if args.list_stream:
print('Loading...') rprint('Loading...')
downloader.show_all_streams(args.url) downloader.show_all_streams(args.url)
if args.raw_info: if args.raw_info:
downloader.show_raw_info(args.url, args.json_prettify) downloader.show_raw_info(args.url, args.json_prettify)
@@ -443,7 +449,7 @@ def main():
# Handle download cases # Handle download cases
if hasattr(args, 'stream') and hasattr(args, 'caption'): if hasattr(args, 'stream') and hasattr(args, 'caption'):
print('Loading...') rprint('Loading...')
if downloader.set_video_info(args.url): if downloader.set_video_info(args.url):
if (args.caption not in downloader.captions.keys()) and (args.caption != 'none'): if (args.caption not in downloader.captions.keys()) and (args.caption != 'none'):
print('\nInvalid caption code or caption not available! Please choose a different caption...!! (use -i to see available captions)') print('\nInvalid caption code or caption not available! Please choose a different caption...!! (use -i to see available captions)')
@@ -460,7 +466,7 @@ def main():
else: else:
downloader.download_stream(args.url, args.stream, args.caption) downloader.download_stream(args.url, args.stream, args.caption)
elif hasattr(args, 'stream'): elif hasattr(args, 'stream'):
print('Loading...') rprint('Loading...')
if downloader.set_video_info(args.url): if downloader.set_video_info(args.url):
if downloader.default_caption == 'none': if downloader.default_caption == 'none':
downloader.download_stream(args.url, args.stream) downloader.download_stream(args.url, args.stream)
@@ -481,7 +487,7 @@ def main():
else: else:
print('Download cancelled! exiting...!!') print('Download cancelled! exiting...!!')
elif hasattr(args, 'caption'): elif hasattr(args, 'caption'):
print('Loading...') rprint('Loading...')
if downloader.set_video_info(args.url): if downloader.set_video_info(args.url):
if (args.caption not in downloader.captions.keys()) and (args.caption != 'none'): if (args.caption not in downloader.captions.keys()) and (args.caption != 'none'):
print('\nInvalid caption code or caption not available! Please choose a different caption...!! (use -i to see available captions)') print('\nInvalid caption code or caption not available! Please choose a different caption...!! (use -i to see available captions)')
@@ -525,7 +531,7 @@ def main():
else: else:
print('Sorry, No downloadable video stream found....!!!') 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 elif not any([args.show_info, args.raw_info, args.json_prettify, args.list_stream]): # If no info flags are set
print('Loading...') rprint('Loading...')
if downloader.set_video_info(args.url): if downloader.set_video_info(args.url):
if downloader.default_stream == 'max' and downloader.maxres: if downloader.default_stream == 'max' and downloader.maxres:
if downloader.default_caption == 'none': if downloader.default_caption == 'none':

View File

@@ -1,6 +1,6 @@
from importlib.metadata import version from importlib.metadata import version
from .config import load_config, get_temporary_directory from .config import load_config, get_temporary_directory
import os, re, subprocess, platform import os, re, subprocess, platform, requests
userConfig = load_config() userConfig = load_config()
downloadDIR = userConfig['downloadDIR'] downloadDIR = userConfig['downloadDIR']
@@ -79,3 +79,32 @@ def clear_temp_files():
print(e) print(e)
else: else:
print('No temporary files found to clear...!') print('No temporary files found to clear...!')
def compare_versions(v1: str, v2: str):
parts1 = list(map(int, v1.split('.')))
parts2 = list(map(int, v2.split('.')))
for i in range(max(len(parts1), len(parts2))):
part1 = parts1[i] if i < len(parts1) else 0
part2 = parts2[i] if i < len(parts2) else 0
if part1 > part2:
return 1
if part1 < part2:
return -1
return 0
def get_platform_specific_upgrade_command():
if platform.system().lower() == 'windows':
return 'pip install pytubefix pytubepp --upgrade; pytubepp --postinstall'
else:
return 'pip3 install pytubefix pytubepp --upgrade && pytubepp --postinstall'
def check_update():
try:
response = requests.get('https://pypi.org/pypi/pytubepp/json')
if response.status_code != 200:
return False, None, None, None
latest_version = response.json()['info']['version']
current_version = get_version()
return compare_versions(current_version, latest_version) == -1, current_version, latest_version, get_platform_specific_upgrade_command()
except Exception as e:
return False, None, None, None

View File

@@ -5,6 +5,7 @@ mutagen
tabulate tabulate
tqdm tqdm
appdirs appdirs
rich
setuptools setuptools
wheel wheel
twine twine