From 40b98d98f8dcc7b8a6da46e825d667989a436c67 Mon Sep 17 00:00:00 2001 From: nathansmithsmith Date: Thu, 9 Nov 2023 13:53:10 -0700 Subject: Added download types --- src/main.py | 2 +- src/model/youload_app.py | 132 ----------------------------------- src/model/youload_file_chooser.py | 53 --------------- src/model/youload_playlist.py | 50 ++++++++++++++ src/view/app.py | 140 ++++++++++++++++++++++++++++++++++++++ src/view/download_type_chooser.py | 33 +++++++++ src/view/file_chooser.py | 51 ++++++++++++++ src/youload_playlist.py | 37 ---------- 8 files changed, 275 insertions(+), 223 deletions(-) delete mode 100644 src/model/youload_app.py delete mode 100644 src/model/youload_file_chooser.py create mode 100644 src/model/youload_playlist.py create mode 100644 src/view/app.py create mode 100644 src/view/download_type_chooser.py create mode 100644 src/view/file_chooser.py delete mode 100644 src/youload_playlist.py diff --git a/src/main.py b/src/main.py index b3a5503..98838fa 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,4 @@ -from model.youload_app import YouloadApp +from view.app import YouloadApp if __name__ == "__main__": app = YouloadApp() diff --git a/src/model/youload_app.py b/src/model/youload_app.py deleted file mode 100644 index 6d20b22..0000000 --- a/src/model/youload_app.py +++ /dev/null @@ -1,132 +0,0 @@ -#! /usr/bin/python3 - -from kivy.app import App -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.button import Button -from kivy.uix.floatlayout import FloatLayout -from kivy.uix.gridlayout import GridLayout -from kivy.uix.textinput import TextInput -from kivy.uix.label import Label -from kivy.uix.filechooser import FileChooser - -import threading -from pathlib import Path -import pytube - -from youload_playlist import YouLoadPlayList -from model.youload_file_chooser import YouloadFileChooser - -class YouloadApp(App): - def build(self): - layout = BoxLayout(orientation='vertical') - - # Data members - self.url = "https://youtube.com/playlist?list=PLuZUmvZz4WI78uqT5S71yBHNBMJ97HzhY&si=SI2qN9MgxmmK2rbf" - self.is_downloading = False - self.should_stop_download = False - - # Url input. - url_input = TextInput(text=self.url, multiline=False, size_hint=(0.8, 1.0)) - url_input.bind(text=self.uid_url_input) - - # Submit button. - self.submit = Button(text="Download", size_hint=(0.2, 1.0)) - self.submit.bind(on_press=self.submit_cb) - - # Url and submit layout. - url_and_submit = BoxLayout(size_hint=(1.0, 0.1)) - url_and_submit.add_widget(url_input) - url_and_submit.add_widget(self.submit) - - # Download status. - self.download_status = Label(text="Nothing downloading", size_hint=(0.4, 1.0)) - - # Folder display. - self.folder_display = Label(text=str(Path.home()), size_hint=(0.4, 1.0)) - - # choose folder button. - self.choose_folder_button = Button(text="Folder", size_hint=(0.2, 1.0)) - self.choose_folder_button.bind(on_press=self.choose_folder_cb) - - # Info line. - info_line = BoxLayout(size_hint=(1.0, 0.1)) - info_line.add_widget(self.download_status) - info_line.add_widget(self.folder_display) - info_line.add_widget(self.choose_folder_button) - - # Info display. - self.downloads_display = Label(text="", size_hint=(1.0, 0.8)) - - # Everything else (: - layout.add_widget(url_and_submit) - layout.add_widget(info_line) - layout.add_widget(self.downloads_display) - - # File chooser. - self.file_chooser = YouloadFileChooser() - self.file_chooser.set_app(self) - - # Screen layout. - self.screen_layout = FloatLayout() - self.screen_layout.add_widget(layout) - - return self.screen_layout - - def uid_url_input(self, instance, value): - self.url = value - - # Download the videos in a different thread so the ui still works. - def download_playlist_thread(self): - try: - self.download_status.text = "Fetching playlist" - self.submit.text = "Stop" - self.is_downloading = True - - # Get playlist. - playlist = YouLoadPlayList(self.url) - - # Set directory and other stuff. - playlist.set_download_directory(self.folder_display.text) - playlist.prepare_for_download() - self.downloads_display.text = f"Downloading to {playlist.folder_name}\n" - - # Download each video - for i in range(playlist.video_count): - # Stop this mother fucker - if self.should_stop_download: - break - - self.download_status.text = f"Downloading {i+1}/{playlist.video_count}" - self.downloads_display.text += playlist.download_video(i) + "\n" - - # Complete download. - self.download_status.text = "Download complete" - except FileExistsError: - self.download_status.text = "Folder already exists" - except KeyError: - self.download_status.text = "Error getting playlist" - - self.submit.text = "Download" - self.is_downloading = False - - def stop_download(self): - self.should_stop_download = True - - def submit_cb(self, instance): - # Is already downloading something. - if self.is_downloading: - self.download_status.text = "Stopping download" - self.stop_download() - return - - # Start download thread. - download_thread = threading.Thread(target=self.download_playlist_thread) - self.should_stop_download = False - download_thread.start() - - def choose_folder_cb(self, instance): - self.screen_layout.add_widget(self.file_chooser) - - def on_stop(self): - self.stop_download() - diff --git a/src/model/youload_file_chooser.py b/src/model/youload_file_chooser.py deleted file mode 100644 index ab37a2d..0000000 --- a/src/model/youload_file_chooser.py +++ /dev/null @@ -1,53 +0,0 @@ -from kivy.uix.popup import Popup -from kivy.uix.button import Button -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.filechooser import FileChooserListView -from kivy.uix.textinput import TextInput -from pathlib import Path -import os - -class YouloadFileChooser(Popup): - - def __init__(self, **kwargs): - super(YouloadFileChooser, self).__init__(**kwargs) - - layout = BoxLayout(orientation='vertical') - - self.title = "Find folder" - - self.dirselect = True - - # Path input. - self.path_input = TextInput(text=str(Path.home()), multiline=False, size_hint=(0.9, 1.0)) - self.path_input.bind(text=self.uid_path_input) - - # Close button. - close_button = Button(text="Close", size_hint=(0.1, 1.0)) - close_button.bind(on_press=self.close_button_cb) - - top_bar_layout = BoxLayout(size_hint=(1.0, 0.1)) - top_bar_layout.add_widget(self.path_input) - top_bar_layout.add_widget(close_button) - - # File chooser. - self.file_chooser = FileChooserListView(size_hint=(1.0, 0.9)) - self.file_chooser.path = str(Path.home()) - - layout.add_widget(top_bar_layout) - layout.add_widget(self.file_chooser) - self.add_widget(layout) - - def set_app(self, app): - self.app = app - - def close_button_cb(self, instance): - self.app.folder_display.text = self.get_folder() - self.parent.remove_widget(self) - - def uid_path_input(self, instance, value): - if os.path.exists(value): - self.file_chooser.path = value - - def get_folder(self): - return self.file_chooser.path - diff --git a/src/model/youload_playlist.py b/src/model/youload_playlist.py new file mode 100644 index 0000000..f23533f --- /dev/null +++ b/src/model/youload_playlist.py @@ -0,0 +1,50 @@ +from pytube import Playlist +import os + +def make_alpha_numeric(string: str) -> str: + return "".join(char for char in string if char.isalnum()) + +class YouLoadPlayList: + """A class for download and handling youtube playlists""" + + DOWNLOAD_TYPES = ["highest", "lowest", "audio", "720p", "480p", "360p", "240p", "144p"] + DEFAULT_DOWNLOAD_TYPE = "highest" + + def __init__(self, link: str): + self.yt_playlist = Playlist(link) + self.video_count = len(self.yt_playlist.videos) + self.folder_name = make_alpha_numeric(self.yt_playlist.title) + self.download_type = self.DEFAULT_DOWNLOAD_TYPE + + def generate_video_info(self, video_num: int) -> str: + """Returns information on video in playlist at 'video_num'""" + + video = self.yt_playlist.videos[video_num] + video_size = video.streams.get_highest_resolution().filesize // 1048576 + return f"Title: {video.title}, Size: {video_size} MB" + + def set_download_directory(self, directory: str) -> None: + """Sets where the playlist folder will be downloaded""" + self.folder_name = os.path.join(directory, make_alpha_numeric(self.yt_playlist.title)) + + def prepare_for_download(self) -> None: + """Gets the playlist ready for download. Creates the output folder and that stuff.""" + os.mkdir(self.folder_name) + + def download_video(self, video_num: int) -> str: + """Download video at 'video_num'""" + video = self.yt_playlist.videos[video_num] + video_size = video.streams.get_highest_resolution().filesize + + # Download this fucker. + if self.download_type == "highest": + video.streams.get_highest_resolution().download(output_path=self.folder_name) + elif self.download_type == "lowest": + video.streams.get_lowest_resolution().download(output_path=self.folder_name) + elif self.download_type == "audio": + video.streams.get_audio_only().download(output_path=self.folder_name) + else: + video.streams.get_by_resolution(self.download_type).download(output_path=self.folder_name) + + return f"Title: {video.title}, Size: {video_size // (1024 ** 2)} MB" + diff --git a/src/view/app.py b/src/view/app.py new file mode 100644 index 0000000..203cc56 --- /dev/null +++ b/src/view/app.py @@ -0,0 +1,140 @@ +#! /usr/bin/python3 + +from kivy.app import App +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.button import Button +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.textinput import TextInput +from kivy.uix.popup import Popup +from kivy.uix.label import Label + +import threading +from pathlib import Path +import os + +from model.youload_playlist import YouLoadPlayList +from view.file_chooser import YouloadFileChooser +from view.download_type_chooser import YouloadDownloadTypeChooser + +class YouloadApp(App): + def build(self): + layout = BoxLayout(orientation='vertical') + + # Data members + self.url = "https://youtube.com/playlist?list=PLXbRgJ9vx_KuA5CljV3BpiV58qx6znmRk&si=JFaVyspkeoMWUDNL" + self.is_downloading = False + self.should_stop_download = False + + # File chooser. + self.file_chooser = YouloadFileChooser() + self.file_chooser.set_app(self) + + # Download type chooser. + self.download_type_chooser = YouloadDownloadTypeChooser() + self.download_type_chooser.set_app(self) + + # Url input. + url_input = TextInput(text=self.url, multiline=False, size_hint=(0.8, 1.0)) + url_input.bind(text=self.uid_url_input) + + # Submit button. + self.submit = Button(text="Download", size_hint=(0.2, 1.0)) + self.submit.bind(on_press=self.submit_cb) + + # Url and submit layout. + url_and_submit = BoxLayout(size_hint=(1.0, 0.1)) + url_and_submit.add_widget(url_input) + url_and_submit.add_widget(self.submit) + + # Download status. + self.download_status = Label(text="Nothing downloading", size_hint=(0.3, 1.0)) + + # Download type chooser button. + self.download_type_button = Button(text=self.download_type_chooser.current_type, size_hint=(0.1, 1.0)) + self.download_type_button.bind(on_press=self.download_type_chooser.open) + + # Folder display. + self.folder_display = Label(text=os.getcwd(), size_hint=(0.4, 1.0)) + + # choose folder button. + self.choose_folder_button = Button(text="Folder", size_hint=(0.2, 1.0)) + self.choose_folder_button.bind(on_press=self.choose_folder_cb) + + # Info line. + info_line = BoxLayout(size_hint=(1.0, 0.1)) + info_line.add_widget(self.download_status) + info_line.add_widget(self.download_type_button) + info_line.add_widget(self.folder_display) + info_line.add_widget(self.choose_folder_button) + + # Info display. + self.downloads_display = Label(text="", size_hint=(1.0, 0.8)) + + # Everything else (: + layout.add_widget(url_and_submit) + layout.add_widget(info_line) + layout.add_widget(self.downloads_display) + + return layout + + def uid_url_input(self, instance, value): + self.url = value + + # Download the videos in a different thread so the ui still works. + def download_playlist_thread(self): + try: + self.download_status.text = "Fetching playlist" + self.submit.text = "Stop" + self.is_downloading = True + + # Get playlist. + playlist = YouLoadPlayList(self.url) + + # Set directory and other stuff. + playlist.set_download_directory(self.folder_display.text) + playlist.download_type = self.download_type_chooser.current_type + playlist.prepare_for_download() + self.downloads_display.text = f"Downloading to {playlist.folder_name}\n" + + # Download each video + for i in range(playlist.video_count): + # Stop this mother fucker + if self.should_stop_download: + break + + self.download_status.text = f"Downloading {i+1}/{playlist.video_count}" + self.downloads_display.text += playlist.download_video(i) + "\n" + + # Complete download. + self.download_status.text = "Download complete" + except FileExistsError: + self.download_status.text = "Folder already exists" + except KeyError: + self.download_status.text = "Error getting playlist" + except AttributeError: + self.download_status.text = "Download type not supported" + + self.submit.text = "Download" + self.is_downloading = False + + def stop_download(self): + self.download_status.text = "Stopping download" + self.should_stop_download = True + + def submit_cb(self, instance): + # Is already downloading something. + if self.is_downloading: + self.stop_download() + return + + # Start download thread. + download_thread = threading.Thread(target=self.download_playlist_thread) + self.should_stop_download = False + download_thread.start() + + def choose_folder_cb(self, instance): + self.file_chooser.open() + + def on_stop(self): + self.stop_download() + diff --git a/src/view/download_type_chooser.py b/src/view/download_type_chooser.py new file mode 100644 index 0000000..927d7a4 --- /dev/null +++ b/src/view/download_type_chooser.py @@ -0,0 +1,33 @@ +from kivy.uix.label import Label +from kivy.uix.button import Button +from kivy.uix.popup import Popup +from kivy.uix.boxlayout import BoxLayout + +from model.youload_playlist import YouLoadPlayList + +class YouloadDownloadTypeChooser(Popup): + + def __init__(self, **kwargs): + super(YouloadDownloadTypeChooser, self).__init__(**kwargs) + + self.title = "Download types" + + layout = BoxLayout(orientation="vertical") + self.current_type = YouLoadPlayList.DEFAULT_DOWNLOAD_TYPE + + # Add option buttons. + for download_type in YouLoadPlayList.DOWNLOAD_TYPES: + item = Button(text=download_type) + item.bind(on_press=self.option_button_cb) + layout.add_widget(item) + + self.content = layout + + def set_app(self, app): + self.app = app + + def option_button_cb(self, instance): + self.current_type = instance.text + self.app.download_type_button.text = self.current_type + self.dismiss() + diff --git a/src/view/file_chooser.py b/src/view/file_chooser.py new file mode 100644 index 0000000..dfba199 --- /dev/null +++ b/src/view/file_chooser.py @@ -0,0 +1,51 @@ +from kivy.uix.popup import Popup +from kivy.uix.button import Button +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.filechooser import FileChooserListView +from kivy.uix.textinput import TextInput +from pathlib import Path +import os + +class YouloadFileChooser(Popup): + + def __init__(self, **kwargs): + super(YouloadFileChooser, self).__init__(**kwargs) + + layout = BoxLayout(orientation='vertical') + + self.title = "Find folder" + + # Path input. + self.path_input = TextInput(text=str(Path.home()), multiline=False, size_hint=(0.9, 1.0)) + self.path_input.bind(text=self.uid_path_input) + + # Close button. + close_button = Button(text="Close", size_hint=(0.1, 1.0)) + close_button.bind(on_press=self.close_button_cb) + + top_bar_layout = BoxLayout(size_hint=(1.0, 0.1)) + top_bar_layout.add_widget(self.path_input) + top_bar_layout.add_widget(close_button) + + # File chooser. + self.file_chooser = FileChooserListView(size_hint=(1.0, 0.9)) + self.file_chooser.path = str(Path.home()) + + layout.add_widget(top_bar_layout) + layout.add_widget(self.file_chooser) + self.content = layout + + def set_app(self, app): + self.app = app + + def close_button_cb(self, instance): + self.app.folder_display.text = self.get_folder() + self.dismiss() + + def uid_path_input(self, instance, value): + if os.path.exists(value): + self.file_chooser.path = value + + def get_folder(self): + return self.file_chooser.path + diff --git a/src/youload_playlist.py b/src/youload_playlist.py deleted file mode 100644 index 803b1c1..0000000 --- a/src/youload_playlist.py +++ /dev/null @@ -1,37 +0,0 @@ -from pytube import Playlist -import os - -def make_alpha_numeric(string: str) -> str: - return "".join(char for char in string if char.isalnum()) - -class YouLoadPlayList: - """A class for download and handling youtube playlists""" - - def __init__(self, link: str): - self.yt_playlist = Playlist(link) - self.video_count = len(self.yt_playlist.videos) - self.folder_name = make_alpha_numeric(self.yt_playlist.title) - - def generate_video_info(self, video_num: int) -> str: - """Returns information on video in playlist at 'video_num'""" - - video = self.yt_playlist.videos[video_num] - video_size = video.streams.get_highest_resolution().filesize // 1048576 - return f"Title: {video.title}, Size: {video_size} MB" - - def set_download_directory(self, directory: str) -> None: - """Sets where the playlist folder will be downloaded""" - self.folder_name = os.path.join(directory, make_alpha_numeric(self.yt_playlist.title)) - - def prepare_for_download(self) -> None: - """Gets the playlist ready for download. Creates the output folder and that stuff.""" - os.mkdir(self.folder_name) - - def download_video(self, video_num: int) -> str: - """Download video at 'video_num'""" - video = self.yt_playlist.videos[video_num] - video_size = video.streams.get_highest_resolution().filesize - video.streams.get_highest_resolution().download(output_path=self.folder_name) - - return f"Title: {video.title}, Size: {video_size // (1024 ** 2)} MB" - -- cgit v1.2.3