From b6e6ab13a6624a14fd00a9a7ecbe8b5df5839af5 Mon Sep 17 00:00:00 2001 From: Benex254 Date: Wed, 5 Jun 2024 00:03:33 +0300 Subject: [PATCH] style:Formatted codebase to pep8 --- .gitignore | 5 +- .project/changes_outline.md | 14 ++ anixstream/Controller/__init__.py | 8 +- anixstream/Controller/anime_screen.py | 13 +- anixstream/Controller/crashlog_screen.py | 12 +- anixstream/Controller/downloads_screen.py | 12 +- anixstream/Controller/help_screen.py | 11 +- anixstream/Controller/home_screen.py | 38 +-- anixstream/Controller/my_list_screen.py | 30 ++- anixstream/Controller/search_screen.py | 10 +- anixstream/Model/__init__.py | 8 +- anixstream/Model/anime_screen.py | 13 +- anixstream/Model/download_screen.py | 1 - anixstream/Model/help_screen.py | 1 - anixstream/Model/home_screen.py | 16 +- anixstream/Model/my_list_screen.py | 20 +- anixstream/Model/search_screen.py | 28 ++- anixstream/Utility/__init__.py | 4 +- anixstream/Utility/animdl_config_manager.py | 11 +- anixstream/Utility/data.py | 151 +++++++++++- anixstream/Utility/media_card_loader.py | 218 +++--------------- anixstream/Utility/observer.py | 1 - anixstream/Utility/show_notification.py | 2 +- anixstream/Utility/user_data_helper.py | 35 ++- anixstream/Utility/utils.py | 10 +- anixstream/Utility/yaml_parser.py | 6 +- anixstream/View/AnimeScreen/anime_screen.py | 25 +- .../View/AnimeScreen/components/__init__.py | 13 +- .../components/animdl_stream_dialog.py | 12 +- .../View/AnimeScreen/components/characters.py | 11 +- .../View/AnimeScreen/components/controls.py | 1 - .../AnimeScreen/components/description.py | 1 - .../components/download_anime_dialog.py | 11 +- .../View/AnimeScreen/components/header.py | 1 - .../AnimeScreen/components/rankings_bar.py | 1 - .../View/AnimeScreen/components/review.py | 3 +- .../View/AnimeScreen/components/side_bar.py | 5 +- .../View/CrashLogScreen/crashlog_screen.py | 6 +- .../DownloadsScreen/components/task_card.py | 7 +- .../View/DownloadsScreen/download_screen.py | 3 +- anixstream/View/HelpScreen/help_screen.py | 6 +- anixstream/View/HomeScreen/home_screen.py | 2 +- .../View/MylistScreen/my_list_screen.py | 4 +- .../View/SearchScreen/components/filters.py | 3 +- .../SearchScreen/components/pagination.py | 3 +- anixstream/View/SearchScreen/search_screen.py | 7 +- anixstream/View/__init__.py | 6 +- anixstream/View/base_screen.py | 7 +- .../components/animdl_dialog/animdl_dialog.py | 3 +- .../media_card/components/media_player.py | 5 +- .../media_card/components/media_popup.py | 59 +++-- .../View/components/media_card/media_card.kv | 2 +- .../View/components/media_card/media_card.py | 46 ++-- anixstream/View/screens.py | 21 +- anixstream/__main__.py | 186 +++++++-------- anixstream/libs/anilist/__init__.py | 4 +- anixstream/libs/anilist/anilist.py | 134 ++++++----- .../libs/anilist/anilist_data_schema.py | 173 +++++++------- anixstream/libs/animdl/animdl_api.py | 66 +++--- anixstream/libs/animdl/animdl_data_helper.py | 27 +-- anixstream/libs/animdl/animdl_exceptions.py | 2 +- anixstream/libs/animdl/animdl_types.py | 21 +- anixstream/libs/animdl/extras.py | 2 +- pyrightconfig.json | 4 + 64 files changed, 816 insertions(+), 755 deletions(-) create mode 100644 .project/changes_outline.md create mode 100644 pyrightconfig.json diff --git a/.gitignore b/.gitignore index 819c527..4e7bc42 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,9 @@ *.mp4 *.mp3 *.ass -vids/* +vids +data/ +crashdump.txt # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -10,7 +12,6 @@ __pycache__/ anixstream.ini # C extensions *.so -data/ # Distribution / packaging .Python build/ diff --git a/.project/changes_outline.md b/.project/changes_outline.md new file mode 100644 index 0000000..44086ff --- /dev/null +++ b/.project/changes_outline.md @@ -0,0 +1,14 @@ +# Changes + +# # only load trailers when needed + +- change how media card loader class works +- create function to get a trailer url and cache it for 360 secs +- refactor the codebase to reflect proposed changes + +# # # change how media card loader class works + +- remove unnecesarry code +- make the media card function to still work so it doesn't cause a break in existing functionality- add a get trailer function which takes a callback + +# # Use one media popup for all the media cards diff --git a/anixstream/Controller/__init__.py b/anixstream/Controller/__init__.py index e016c78..1563d9e 100644 --- a/anixstream/Controller/__init__.py +++ b/anixstream/Controller/__init__.py @@ -1,7 +1,7 @@ -from .home_screen import HomeScreenController -from .search_screen import SearchScreenController -from .my_list_screen import MyListScreenController from .anime_screen import AnimeScreenController +from .crashlog_screen import CrashLogScreenController from .downloads_screen import DownloadsScreenController from .help_screen import HelpScreenController -from .crashlog_screen import CrashLogScreenController \ No newline at end of file +from .home_screen import HomeScreenController +from .my_list_screen import MyListScreenController +from .search_screen import SearchScreenController diff --git a/anixstream/Controller/anime_screen.py b/anixstream/Controller/anime_screen.py index ea4215b..a08d6e0 100644 --- a/anixstream/Controller/anime_screen.py +++ b/anixstream/Controller/anime_screen.py @@ -1,16 +1,16 @@ +from kivy.cache import Cache from kivy.clock import Clock from kivy.logger import Logger -from kivy.cache import Cache -from anixstream.Model import AnimeScreenModel -from anixstream.View import AnimeScreenView +from ..Model import AnimeScreenModel +from ..View import AnimeScreenView Cache.register("data.anime", limit=20, timeout=600) class AnimeScreenController: - """The controller for the anime screen - """ + """The controller for the anime screen""" + def __init__(self, model: AnimeScreenModel): self.model = model self.view = AnimeScreenView(controller=self, model=self.model) @@ -19,7 +19,7 @@ class AnimeScreenController: return self.view def update_anime_view(self, id: int, caller_screen_name: str): - """method called to update the anime screen when a new + """method called to update the anime screen when a new Args: id (int): the anilst id of the anime @@ -32,7 +32,6 @@ class AnimeScreenController: data = self.model.get_anime_data(id) if data[0]: - self.model.anime_id = id Clock.schedule_once( lambda _: self.view.update_layout( diff --git a/anixstream/Controller/crashlog_screen.py b/anixstream/Controller/crashlog_screen.py index ee5ecbb..e99dbce 100644 --- a/anixstream/Controller/crashlog_screen.py +++ b/anixstream/Controller/crashlog_screen.py @@ -1,13 +1,11 @@ - -from anixstream.View import CrashLogScreenView -from anixstream.Model import CrashLogScreenModel - +from ..View import CrashLogScreenView +from ..Model import CrashLogScreenModel class CrashLogScreenController: - """The crash log screen controller - """ - def __init__(self, model:CrashLogScreenModel): + """The crash log screen controller""" + + def __init__(self, model: CrashLogScreenModel): self.model = model self.view = CrashLogScreenView(controller=self, model=self.model) # self.update_anime_view() diff --git a/anixstream/Controller/downloads_screen.py b/anixstream/Controller/downloads_screen.py index c626dce..da57abd 100644 --- a/anixstream/Controller/downloads_screen.py +++ b/anixstream/Controller/downloads_screen.py @@ -1,15 +1,13 @@ - -from anixstream.View import DownloadsScreenView -from anixstream.Model import DownloadsScreenModel +from ..Model import DownloadsScreenModel +from ..View import DownloadsScreenView class DownloadsScreenController: - """The controller for the download screen - """ - def __init__(self, model:DownloadsScreenModel): + """The controller for the download screen""" + + def __init__(self, model: DownloadsScreenModel): self.model = model self.view = DownloadsScreenView(controller=self, model=self.model) def get_view(self) -> DownloadsScreenView: return self.view - diff --git a/anixstream/Controller/help_screen.py b/anixstream/Controller/help_screen.py index 915474a..165fa6c 100644 --- a/anixstream/Controller/help_screen.py +++ b/anixstream/Controller/help_screen.py @@ -1,12 +1,11 @@ - -from anixstream.View import HelpScreenView -from anixstream.Model import HelpScreenModel +from ..Model import HelpScreenModel +from ..View import HelpScreenView class HelpScreenController: - """The help screen controller - """ - def __init__(self, model:HelpScreenModel): + """The help screen controller""" + + def __init__(self, model: HelpScreenModel): self.model = model self.view = HelpScreenView(controller=self, model=self.model) diff --git a/anixstream/Controller/home_screen.py b/anixstream/Controller/home_screen.py index 9a353e6..dd5f20c 100644 --- a/anixstream/Controller/home_screen.py +++ b/anixstream/Controller/home_screen.py @@ -1,13 +1,12 @@ - from inspect import isgenerator from kivy.clock import Clock from kivy.logger import Logger -from anixstream.View import HomeScreenView -from anixstream.Model import HomeScreenModel -from anixstream.View.components import MediaCardsContainer -from anixstream.Utility import show_notification +from ..Model import HomeScreenModel +from ..Utility import show_notification +from ..View import HomeScreenView +from ..View.components import MediaCardsContainer # TODO:Move the update home screen to homescreen.py @@ -18,9 +17,10 @@ class HomeScreenController: The controller implements the strategy pattern. The controller connects to the view to control its actions. """ + populate_errors = [] - def __init__(self, model:HomeScreenModel): + def __init__(self, model: HomeScreenModel): self.model = model # Model.main_screen.MainScreenModel self.view = HomeScreenView(controller=self, model=self.model) # if self.view.app.config.get("Preferences","is_startup_anime_enable")=="1": # type: ignore @@ -68,7 +68,7 @@ class HomeScreenController: Logger.error("Home Screen:Failed to load trending anime") self.populate_errors.append("trending Anime") - def highest_scored_anime(self): + def highest_scored_anime(self): most_scored_cards_container = MediaCardsContainer() most_scored_cards_container.list_name = "Most Scored" most_scored_cards_generator = self.model.get_most_scored_anime() @@ -84,7 +84,9 @@ class HomeScreenController: def recently_updated_anime(self): most_recently_updated_cards_container = MediaCardsContainer() most_recently_updated_cards_container.list_name = "Most Recently Updated" - most_recently_updated_cards_generator = self.model.get_most_recently_updated_anime() + most_recently_updated_cards_generator = ( + self.model.get_most_recently_updated_anime() + ) if isgenerator(most_recently_updated_cards_generator): for card in most_recently_updated_cards_generator: card.screen = self.view @@ -94,7 +96,7 @@ class HomeScreenController: Logger.error("Home Screen:Failed to load recently updated anime") self.populate_errors.append("Most recently updated Anime") - def upcoming_anime(self): + def upcoming_anime(self): upcoming_cards_container = MediaCardsContainer() upcoming_cards_container.list_name = "Upcoming Anime" upcoming_cards_generator = self.model.get_upcoming_anime() @@ -109,13 +111,15 @@ class HomeScreenController: def populate_home_screen(self): self.populate_errors = [] - Clock.schedule_once(lambda _:self.trending_anime(),1) - Clock.schedule_once(lambda _:self.highest_scored_anime(),2) - Clock.schedule_once(lambda _:self.popular_anime(),3) - Clock.schedule_once(lambda _: self.favourite_anime(),4) - Clock.schedule_once(lambda _:self.recently_updated_anime(),5) - Clock.schedule_once(lambda _:self.upcoming_anime(),6) + Clock.schedule_once(lambda _: self.trending_anime(), 1) + Clock.schedule_once(lambda _: self.highest_scored_anime(), 2) + Clock.schedule_once(lambda _: self.popular_anime(), 3) + Clock.schedule_once(lambda _: self.favourite_anime(), 4) + Clock.schedule_once(lambda _: self.recently_updated_anime(), 5) + Clock.schedule_once(lambda _: self.upcoming_anime(), 6) if self.populate_errors: - show_notification(f"Failed to fetch all home screen data",f"Theres probably a problem with your internet connection or anilist servers are down.\nFailed include:{', '.join(self.populate_errors)}") - + show_notification( + "Failed to fetch all home screen data", + f"Theres probably a problem with your internet connection or anilist servers are down.\nFailed include:{', '.join(self.populate_errors)}", + ) diff --git a/anixstream/Controller/my_list_screen.py b/anixstream/Controller/my_list_screen.py index adfa6b5..2073394 100644 --- a/anixstream/Controller/my_list_screen.py +++ b/anixstream/Controller/my_list_screen.py @@ -1,12 +1,14 @@ from inspect import isgenerator from kivy.logger import Logger + # from kivy.clock import Clock from kivy.utils import difference -from anixstream.View import MyListScreenView -from anixstream.Model import MyListScreenModel -from anixstream.Utility import user_data_helper +from ..Model import MyListScreenModel +from ..Utility import user_data_helper +from ..View import MyListScreenView + class MyListScreenController: """ @@ -16,24 +18,26 @@ class MyListScreenController: the view to control its actions. """ - def __init__(self, model:MyListScreenModel): - self.model = model + def __init__(self, model: MyListScreenModel): + self.model = model self.view = MyListScreenView(controller=self, model=self.model) - if len(self.requested_update_my_list_screen())>30: + if len(self.requested_update_my_list_screen()) > 30: self.requested_update_my_list_screen(2) def get_view(self) -> MyListScreenView: return self.view - def requested_update_my_list_screen(self,page=None): + def requested_update_my_list_screen(self, page=None): user_anime_list = user_data_helper.get_user_anime_list() - if animes_to_add:=difference(user_anime_list,self.model.already_in_user_anime_list): + if animes_to_add := difference( + user_anime_list, self.model.already_in_user_anime_list + ): Logger.info("My List Screen:User anime list change;updating screen") - # if thirty:=len(animes_to_add)>30: + # if thirty:=len(animes_to_add)>30: # self.model.already_in_user_anime_list = user_anime_list[:30] # else: - - anime_cards = self.model.update_my_anime_list_view(animes_to_add,page) + + anime_cards = self.model.update_my_anime_list_view(animes_to_add, page) self.model.already_in_user_anime_list = user_anime_list if isgenerator(anime_cards): @@ -42,7 +46,9 @@ class MyListScreenController: self.view.update_layout(result_card) return animes_to_add elif page: - anime_cards = self.model.update_my_anime_list_view(self.model.already_in_user_anime_list,page) + anime_cards = self.model.update_my_anime_list_view( + self.model.already_in_user_anime_list, page + ) # self.model.already_in_user_anime_list = user_anime_list if isgenerator(anime_cards): for result_card in anime_cards: diff --git a/anixstream/Controller/search_screen.py b/anixstream/Controller/search_screen.py index 702317d..bb8055a 100644 --- a/anixstream/Controller/search_screen.py +++ b/anixstream/Controller/search_screen.py @@ -3,13 +3,12 @@ from inspect import isgenerator from kivy.clock import Clock from kivy.logger import Logger -from anixstream.View import SearchScreenView -from anixstream.Model import SearchScreenModel +from ..Model import SearchScreenModel +from ..View import SearchScreenView class SearchScreenController: - """The search screen controller - """ + """The search screen controller""" def __init__(self, model: SearchScreenModel): self.model = model @@ -19,8 +18,7 @@ class SearchScreenController: return self.view def update_trending_anime(self): - """Gets and adds the trending anime to the search screen - """ + """Gets and adds the trending anime to the search screen""" trending_cards_generator = self.model.get_trending_anime() if isgenerator(trending_cards_generator): self.view.trending_anime_sidebar.clear_widgets() diff --git a/anixstream/Model/__init__.py b/anixstream/Model/__init__.py index 8f17a6f..bfaf615 100644 --- a/anixstream/Model/__init__.py +++ b/anixstream/Model/__init__.py @@ -1,7 +1,7 @@ -from .home_screen import HomeScreenModel -from .search_screen import SearchScreenModel -from .my_list_screen import MyListScreenModel from .anime_screen import AnimeScreenModel from .crashlog_screen import CrashLogScreenModel +from .download_screen import DownloadsScreenModel from .help_screen import HelpScreenModel -from .download_screen import DownloadsScreenModel \ No newline at end of file +from .home_screen import HomeScreenModel +from .my_list_screen import MyListScreenModel +from .search_screen import SearchScreenModel diff --git a/anixstream/Model/anime_screen.py b/anixstream/Model/anime_screen.py index fde4867..f03a69b 100644 --- a/anixstream/Model/anime_screen.py +++ b/anixstream/Model/anime_screen.py @@ -1,13 +1,12 @@ +from ..libs.anilist import AniList from .base_model import BaseScreenModel -from anixstream.libs.anilist import AniList + class AnimeScreenModel(BaseScreenModel): - """the Anime screen model - """ + """the Anime screen model""" + data = {} anime_id = 0 - - def get_anime_data(self,id:int): + + def get_anime_data(self, id: int): return AniList.get_anime(id) - - diff --git a/anixstream/Model/download_screen.py b/anixstream/Model/download_screen.py index bd0f893..b4513c9 100644 --- a/anixstream/Model/download_screen.py +++ b/anixstream/Model/download_screen.py @@ -5,4 +5,3 @@ class DownloadsScreenModel(BaseScreenModel): """ Handles the download screen logic """ - diff --git a/anixstream/Model/help_screen.py b/anixstream/Model/help_screen.py index 3500772..8a87e28 100644 --- a/anixstream/Model/help_screen.py +++ b/anixstream/Model/help_screen.py @@ -1,7 +1,6 @@ from .base_model import BaseScreenModel - class HelpScreenModel(BaseScreenModel): """ Handles the help screen logic diff --git a/anixstream/Model/home_screen.py b/anixstream/Model/home_screen.py index 6b4b35b..a55dd18 100644 --- a/anixstream/Model/home_screen.py +++ b/anixstream/Model/home_screen.py @@ -1,6 +1,6 @@ +from ..libs.anilist import AniList +from ..Utility.media_card_loader import media_card_loader from .base_model import BaseScreenModel -from anixstream.libs.anilist import AniList -from anixstream.Utility.media_card_loader import MediaCardLoader class HomeScreenModel(BaseScreenModel): @@ -12,7 +12,7 @@ class HomeScreenModel(BaseScreenModel): def _data_generator(): for anime_item in data["data"]["Page"]["media"]: - yield MediaCardLoader.media_card(anime_item) + yield media_card_loader.media_card(anime_item) return _data_generator() else: @@ -24,7 +24,7 @@ class HomeScreenModel(BaseScreenModel): def _data_generator(): for anime_item in data["data"]["Page"]["media"]: - yield MediaCardLoader.media_card(anime_item) + yield media_card_loader.media_card(anime_item) return _data_generator() else: @@ -36,7 +36,7 @@ class HomeScreenModel(BaseScreenModel): def _data_generator(): for anime_item in data["data"]["Page"]["media"]: - yield MediaCardLoader.media_card(anime_item) + yield media_card_loader.media_card(anime_item) return _data_generator() else: @@ -48,7 +48,7 @@ class HomeScreenModel(BaseScreenModel): def _data_generator(): for anime_item in data["data"]["Page"]["media"]: - yield MediaCardLoader.media_card(anime_item) + yield media_card_loader.media_card(anime_item) return _data_generator() else: @@ -60,7 +60,7 @@ class HomeScreenModel(BaseScreenModel): def _data_generator(): for anime_item in data["data"]["Page"]["media"]: - yield MediaCardLoader.media_card(anime_item) + yield media_card_loader.media_card(anime_item) return _data_generator() else: @@ -72,7 +72,7 @@ class HomeScreenModel(BaseScreenModel): def _data_generator(): for anime_item in data["data"]["Page"]["media"]: - yield MediaCardLoader.media_card(anime_item) + yield media_card_loader.media_card(anime_item) return _data_generator() else: diff --git a/anixstream/Model/my_list_screen.py b/anixstream/Model/my_list_screen.py index e0857c0..2e4af8f 100644 --- a/anixstream/Model/my_list_screen.py +++ b/anixstream/Model/my_list_screen.py @@ -1,18 +1,24 @@ from anixstream.libs.anilist import AniList +from anixstream.Utility import media_card_loader, show_notification + from .base_model import BaseScreenModel -from anixstream.Utility import MediaCardLoader,show_notification class MyListScreenModel(BaseScreenModel): already_in_user_anime_list = [] - def update_my_anime_list_view(self,not_yet_in_user_anime_list:list,page=None): - success,self.data = AniList.search(id_in=not_yet_in_user_anime_list,page=page,sort="SCORE_DESC") - if success: + + def update_my_anime_list_view(self, not_yet_in_user_anime_list: list, page=None): + success, self.data = AniList.search( + id_in=not_yet_in_user_anime_list, page=page, sort="SCORE_DESC" + ) + if success: return self.media_card_generator() else: - show_notification(f"Failed to update my list screen view",self.data["Error"]) + show_notification( + "Failed to update my list screen view", self.data["Error"] + ) return None - + def media_card_generator(self): for anime_item in self.data["data"]["Page"]["media"]: - yield MediaCardLoader.media_card(anime_item) + yield media_card_loader.media_card(anime_item) diff --git a/anixstream/Model/search_screen.py b/anixstream/Model/search_screen.py index d439349..1765380 100644 --- a/anixstream/Model/search_screen.py +++ b/anixstream/Model/search_screen.py @@ -1,29 +1,33 @@ +from ..libs.anilist import AniList +from ..Utility import media_card_loader, show_notification from .base_model import BaseScreenModel -from anixstream.libs.anilist import AniList -from anixstream.Utility import MediaCardLoader, show_notification class SearchScreenModel(BaseScreenModel): data = {} - + def get_trending_anime(self): - success,data = AniList.get_trending() + success, data = AniList.get_trending() if success: + def _data_generator(): for anime_item in data["data"]["Page"]["media"]: - yield MediaCardLoader.media_card(anime_item) + yield media_card_loader.media_card(anime_item) + return _data_generator() else: return data - - def search_for_anime(self,anime_title,**kwargs): - success,self.data = AniList.search(query=anime_title,**kwargs) - if success: + + def search_for_anime(self, anime_title, **kwargs): + success, self.data = AniList.search(query=anime_title, **kwargs) + if success: return self.media_card_generator() else: - show_notification(f"Failed to search for {anime_title}",self.data.get("Error")) - + show_notification( + f"Failed to search for {anime_title}", self.data.get("Error") + ) + def media_card_generator(self): for anime_item in self.data["data"]["Page"]["media"]: - yield MediaCardLoader.media_card(anime_item) + yield media_card_loader.media_card(anime_item) self.pagination_info = self.data["data"]["Page"]["pageInfo"] diff --git a/anixstream/Utility/__init__.py b/anixstream/Utility/__init__.py index aebc56c..f75b8ee 100644 --- a/anixstream/Utility/__init__.py +++ b/anixstream/Utility/__init__.py @@ -1,4 +1,4 @@ -from .media_card_loader import MediaCardLoader -from .show_notification import show_notification from .data import themes_available +from .media_card_loader import media_card_loader +from .show_notification import show_notification from .utils import write_crash diff --git a/anixstream/Utility/animdl_config_manager.py b/anixstream/Utility/animdl_config_manager.py index 9203ed1..e75684e 100644 --- a/anixstream/Utility/animdl_config_manager.py +++ b/anixstream/Utility/animdl_config_manager.py @@ -1,6 +1,7 @@ -from typing import TypedDict -import plyer import os +from typing import TypedDict + +import plyer from .yaml_parser import YamlParser @@ -10,9 +11,9 @@ class AnimdlConfig(TypedDict): default_provider: str quality_string: str - -if local_data_path:=os.getenv("LOCALAPPDATA"): - config_dir = os.path.join(local_data_path,".config") + +if local_data_path := os.getenv("LOCALAPPDATA"): + config_dir = os.path.join(local_data_path, ".config") if not os.path.exists(config_dir): os.mkdir(config_dir) animdl_config_folder_location = os.path.join(config_dir, "animdl") diff --git a/anixstream/Utility/data.py b/anixstream/Utility/data.py index cb8a514..1017bb6 100644 --- a/anixstream/Utility/data.py +++ b/anixstream/Utility/data.py @@ -2,5 +2,152 @@ Just contains some useful data used across the codebase """ -themes_available = ['Aliceblue', 'Antiquewhite', 'Aqua', 'Aquamarine', 'Azure', 'Beige', 'Bisque', 'Black', 'Blanchedalmond', 'Blue', 'Blueviolet', 'Brown', 'Burlywood', 'Cadetblue', 'Chartreuse', 'Chocolate', 'Coral', 'Cornflowerblue', 'Cornsilk', 'Crimson', 'Cyan', 'Darkblue', 'Darkcyan', 'Darkgoldenrod', 'Darkgray', 'Darkgrey', 'Darkgreen', 'Darkkhaki', 'Darkmagenta', 'Darkolivegreen', 'Darkorange', 'Darkorchid', 'Darkred', 'Darksalmon', 'Darkseagreen', 'Darkslateblue', 'Darkslategray', 'Darkslategrey', 'Darkturquoise', 'Darkviolet', 'Deeppink', 'Deepskyblue', 'Dimgray', 'Dimgrey', 'Dodgerblue', 'Firebrick', 'Floralwhite', 'Forestgreen', 'Fuchsia', 'Gainsboro', 'Ghostwhite', 'Gold', 'Goldenrod', 'Gray', 'Grey', 'Green', 'Greenyellow', 'Honeydew', 'Hotpink', 'Indianred', 'Indigo', 'Ivory', 'Khaki', 'Lavender', 'Lavenderblush', 'Lawngreen', 'Lemonchiffon', 'Lightblue', 'Lightcoral', 'Lightcyan', 'Lightgoldenrodyellow', 'Lightgreen', 'Lightgray', 'Lightgrey', 'Lightpink', 'Lightsalmon', 'Lightseagreen', 'Lightskyblue', 'Lightslategray', 'Lightslategrey', 'Lightsteelblue', 'Lightyellow', 'Lime', 'Limegreen', 'Linen', 'Magenta', 'Maroon', 'Mediumaquamarine', 'Mediumblue', 'Mediumorchid', 'Mediumpurple', 'Mediumseagreen', 'Mediumslateblue', 'Mediumspringgreen', 'Mediumturquoise', 'Mediumvioletred', 'Midnightblue', 'Mintcream', 'Mistyrose', 'Moccasin', 'Navajowhite', 'Navy', 'Oldlace', 'Olive', 'Olivedrab', 'Orange', 'Orangered', 'Orchid', 'Palegoldenrod', 'Palegreen', 'Paleturquoise', 'Palevioletred', 'Papayawhip', 'Peachpuff', 'Peru', 'Pink', 'Plum', 'Powderblue', 'Purple', 'Red', 'Rosybrown', 'Royalblue', 'Saddlebrown', 'Salmon', 'Sandybrown', 'Seagreen', 'Seashell', 'Sienna', 'Silver', 'Skyblue', 'Slateblue', 'Slategray', 'Slategrey', 'Snow', 'Springgreen', 'Steelblue', 'Tan', 'Teal', 'Thistle', 'Tomato', 'Turquoise', 'Violet', 'Wheat', 'White', 'Whitesmoke', 'Yellow', -'Yellowgreen'] \ No newline at end of file +themes_available = [ + "Aliceblue", + "Antiquewhite", + "Aqua", + "Aquamarine", + "Azure", + "Beige", + "Bisque", + "Black", + "Blanchedalmond", + "Blue", + "Blueviolet", + "Brown", + "Burlywood", + "Cadetblue", + "Chartreuse", + "Chocolate", + "Coral", + "Cornflowerblue", + "Cornsilk", + "Crimson", + "Cyan", + "Darkblue", + "Darkcyan", + "Darkgoldenrod", + "Darkgray", + "Darkgrey", + "Darkgreen", + "Darkkhaki", + "Darkmagenta", + "Darkolivegreen", + "Darkorange", + "Darkorchid", + "Darkred", + "Darksalmon", + "Darkseagreen", + "Darkslateblue", + "Darkslategray", + "Darkslategrey", + "Darkturquoise", + "Darkviolet", + "Deeppink", + "Deepskyblue", + "Dimgray", + "Dimgrey", + "Dodgerblue", + "Firebrick", + "Floralwhite", + "Forestgreen", + "Fuchsia", + "Gainsboro", + "Ghostwhite", + "Gold", + "Goldenrod", + "Gray", + "Grey", + "Green", + "Greenyellow", + "Honeydew", + "Hotpink", + "Indianred", + "Indigo", + "Ivory", + "Khaki", + "Lavender", + "Lavenderblush", + "Lawngreen", + "Lemonchiffon", + "Lightblue", + "Lightcoral", + "Lightcyan", + "Lightgoldenrodyellow", + "Lightgreen", + "Lightgray", + "Lightgrey", + "Lightpink", + "Lightsalmon", + "Lightseagreen", + "Lightskyblue", + "Lightslategray", + "Lightslategrey", + "Lightsteelblue", + "Lightyellow", + "Lime", + "Limegreen", + "Linen", + "Magenta", + "Maroon", + "Mediumaquamarine", + "Mediumblue", + "Mediumorchid", + "Mediumpurple", + "Mediumseagreen", + "Mediumslateblue", + "Mediumspringgreen", + "Mediumturquoise", + "Mediumvioletred", + "Midnightblue", + "Mintcream", + "Mistyrose", + "Moccasin", + "Navajowhite", + "Navy", + "Oldlace", + "Olive", + "Olivedrab", + "Orange", + "Orangered", + "Orchid", + "Palegoldenrod", + "Palegreen", + "Paleturquoise", + "Palevioletred", + "Papayawhip", + "Peachpuff", + "Peru", + "Pink", + "Plum", + "Powderblue", + "Purple", + "Red", + "Rosybrown", + "Royalblue", + "Saddlebrown", + "Salmon", + "Sandybrown", + "Seagreen", + "Seashell", + "Sienna", + "Silver", + "Skyblue", + "Slateblue", + "Slategray", + "Slategrey", + "Snow", + "Springgreen", + "Steelblue", + "Tan", + "Teal", + "Thistle", + "Tomato", + "Turquoise", + "Violet", + "Wheat", + "White", + "Whitesmoke", + "Yellow", + "Yellowgreen", +] diff --git a/anixstream/Utility/media_card_loader.py b/anixstream/Utility/media_card_loader.py index 5a8e5bc..bf168a1 100644 --- a/anixstream/Utility/media_card_loader.py +++ b/anixstream/Utility/media_card_loader.py @@ -1,152 +1,21 @@ -from collections import deque -from time import sleep -import threading - +from kivy.cache import Cache +from kivy.logger import Logger from pytube import YouTube -from kivy.clock import Clock -from kivy.cache import Cache -from kivy.loader import _ThreadPool -from kivy.logger import Logger +from ..libs.anilist.anilist_data_schema import AnilistBaseMediaDataSchema +from ..Utility import anilist_data_helper, user_data_helper +from ..View.components import MediaCard -from anixstream.View.components import MediaCard -from anixstream.Utility import anilist_data_helper, user_data_helper -from anixstream.libs.anilist.anilist_data_schema import AnilistBaseMediaDataSchema +Cache.register("trailer_urls.anime", timeout=360) -# Register anime cache in memory -Cache.register("yt_stream_links.anime") - -user_anime_list = user_data_helper.get_user_anime_list() - -yt_stream_links = user_data_helper.get_anime_trailer_cache() -for link in yt_stream_links: - Cache.append("yt_stream_links.anime", link[0], tuple(link[1])) - - -# TODO: Make this process more efficient -# for youtube video links gotten from from pytube which is blocking class MediaCardDataLoader(object): """this class loads an anime media card and gets the trailer url from pytube""" - def __init__(self): - self._resume_cond = threading.Condition() - self._num_workers = 5 - self._max_upload_per_frame = 5 - self._paused = False - self._q_load = deque() - self._q_done = deque() - self._client = [] - self._running = False - self._start_wanted = False - self._trigger_update = Clock.create_trigger(self._update) - - def start(self): - """Start the loader thread/process.""" - self._running = True - - def run(self, *largs): - """Main loop for the loader.""" - pass - - def stop(self): - """Stop the loader thread/process.""" - self._running = False - - def pause(self): - """Pause the loader, can be useful during interactions. - - .. versionadded:: 1.6.0 - """ - self._paused = True - - def resume(self): - """Resume the loader, after a :meth:`pause`. - - .. versionadded:: 1.6.0 - """ - self._paused = False - self._resume_cond.acquire() - self._resume_cond.notify_all() - self._resume_cond.release() - - def _wait_for_resume(self): - while self._running and self._paused: - self._resume_cond.acquire() - self._resume_cond.wait(0.25) - self._resume_cond.release() - - def cached_fetch_data(self, yt_watch_url): - data: tuple = Cache.get("yt_stream_links.anime", yt_watch_url) # type: ignore # trailer_url is the yt_watch_link - - if not data[0]: - yt = YouTube(yt_watch_url) - preview_image = yt.thumbnail_url - try: - video_stream_url = yt.streams.filter( - progressive=True, file_extension="mp4" - )[-1].url - data = preview_image, video_stream_url - yt_stream_links.append((yt_watch_url, data)) - user_data_helper.update_anime_trailer_cache(yt_stream_links) - except: - data = preview_image, None - return data - - def _load(self, kwargs): - while len(self._q_done) >= (self._max_upload_per_frame * self._num_workers): - sleep(0.1) # type: ignore - self._wait_for_resume() - yt_watch_link = kwargs["yt_watch_link"] - try: - data = self.cached_fetch_data(yt_watch_link) - except Exception as e: - data = None - Logger.error("Pytube:{e}") - - self._q_done.appendleft((yt_watch_link, data)) - self._trigger_update() - - def _update(self, *largs): - if self._start_wanted: - if not self._running: - self.start() - self._start_wanted = False - - # in pause mode, don't unqueue anything. - if self._paused: - self._trigger_update() - return - - for _ in range(self._max_upload_per_frame): - try: - yt_watch_link, data = self._q_done.pop() - except IndexError: - return - # update client - for c_yt_watch_link, client in self._client[:]: - if yt_watch_link != c_yt_watch_link: - continue - - # got one client to update - if data: - trailer_url = data[1] - if trailer_url: - client.set_trailer_url(trailer_url) - Logger.info(f"Pytube:Found trailer url for {client.title}") - Cache.append("yt_stream_links.anime", yt_watch_link, data) - self._client.remove((c_yt_watch_link, client)) - - self._trigger_update() - def media_card( self, anime_item: AnilistBaseMediaDataSchema, - load_callback=None, - post_callback=None, - **kwargs, ): - media_card = MediaCard() media_card.anime_id = anime_id = anime_item["id"] @@ -207,7 +76,7 @@ class MediaCardDataLoader(object): anime_item["genres"] ) - if anime_id in user_anime_list: + if anime_id in user_data_helper.get_user_anime_list(): media_card.is_in_my_list = True if anime_item["averageScore"]: @@ -216,61 +85,26 @@ class MediaCardDataLoader(object): for i in range(stars): media_card.stars[i] = 1 - # Setting up trailer info to be gotten if available - if anime_item["trailer"]: - yt_watch_link = "https://youtube.com/watch?v=" + anime_item["trailer"]["id"] - data = Cache.get("yt_stream_links.anime", yt_watch_link) # type: ignore # trailer_url is the yt_watch_link - if data: - if data[1] not in (None, False): - media_card.set_preview_image(data[0]) - media_card.set_trailer_url(data[1]) - return media_card - else: - # if data is None, this is really the first time - self._client.append((yt_watch_link, media_card)) - self._q_load.appendleft( - { - "yt_watch_link": yt_watch_link, - "load_callback": load_callback, - "post_callback": post_callback, - "current_anime": anime_item["id"], - "kwargs": kwargs, - } - ) - if not kwargs.get("nocache", False): - Cache.append("yt_stream_links.anime", yt_watch_link, (False, False)) - self._start_wanted = True - self._trigger_update() + if trailer := anime_item.get("trailer"): + trailer_url = "https://youtube.com/watch/v=" + trailer["id"] + media_card._trailer_url = trailer_url return media_card - -class LoaderThreadPool(MediaCardDataLoader): - def __init__(self): - super(LoaderThreadPool, self).__init__() - self.pool: _ThreadPool | None = None - - def start(self): - super(LoaderThreadPool, self).start() - self.pool = _ThreadPool(self._num_workers) - Clock.schedule_interval(self.run, 0) - - def stop(self): - super(LoaderThreadPool, self).stop() - Clock.unschedule(self.run) - self.pool.stop() # type: ignore - - def run(self, *largs): - while self._running: - try: - parameters = self._q_load.pop() - except: - return - self.pool.add_task(self._load, parameters) # type: ignore + def get_trailer_from_pytube(self, trailer_url, anime): + if trailer := Cache.get("trailer_urls.anime", trailer_url): + return trailer + try: + yt = YouTube(trailer_url) + trailer = yt.streams.filter( + progressive=True, + file_extension="mp4", + )[-1].url + Logger.info(f"Pytube Success:For {anime}") + Cache.append("trailer_urls.anime", trailer_url, trailer) + return trailer + except Exception as e: + Logger.error(f"Pytube Failure:For {anime} reason: {e}") + return "" -MediaCardLoader = LoaderThreadPool() -Logger.info( - "MediaCardLoader: using a thread pool of {} workers".format( - MediaCardLoader._num_workers - ) -) +media_card_loader = MediaCardDataLoader() diff --git a/anixstream/Utility/observer.py b/anixstream/Utility/observer.py index 3db96a1..bbf1a9f 100644 --- a/anixstream/Utility/observer.py +++ b/anixstream/Utility/observer.py @@ -1,4 +1,3 @@ - # Of course, "very flexible Python" allows you to do without an abstract # superclass at all or use the clever exception `NotImplementedError`. In my # opinion, this can negatively affect the architecture of the application. diff --git a/anixstream/Utility/show_notification.py b/anixstream/Utility/show_notification.py index d6e6ad7..0e3098e 100644 --- a/anixstream/Utility/show_notification.py +++ b/anixstream/Utility/show_notification.py @@ -1,5 +1,5 @@ -from kivymd.uix.snackbar import MDSnackbar, MDSnackbarText, MDSnackbarSupportingText from kivy.clock import Clock +from kivymd.uix.snackbar import MDSnackbar, MDSnackbarSupportingText, MDSnackbarText def show_notification(title, details): diff --git a/anixstream/Utility/user_data_helper.py b/anixstream/Utility/user_data_helper.py index 7f06f75..40b15d8 100644 --- a/anixstream/Utility/user_data_helper.py +++ b/anixstream/Utility/user_data_helper.py @@ -2,16 +2,24 @@ Contains Helper functions to read and write the user data files """ -from kivy.storage.jsonstore import JsonStore +import os from datetime import date, datetime -from kivy.logger import Logger -from kivy.resources import resource_find +from kivy.logger import Logger + +from kivy.storage.jsonstore import JsonStore + +app_dir = os.path.dirname(__file__) +data_folder = os.path.join(app_dir, "data") today = date.today() now = datetime.now() -user_data = JsonStore(resource_find("user_data.json")) -yt_cache = JsonStore(resource_find("yt_cache.json")) +if user_data_path := os.path.exists(os.path.join(data_folder, "user_data.json")): + user_data = JsonStore(user_data_path) +else: + os.makedirs(data_folder, exist_ok=True) + user_data_path = os.path.join(data_folder, "user_data.json") + user_data = JsonStore(user_data_path) # Get the user data @@ -63,20 +71,3 @@ else: t = 4 yt_anime_trailer_cache_name = f"{today}{t}" - - -def get_anime_trailer_cache() -> list: - try: - return yt_cache["yt_stream_links"][f"{yt_anime_trailer_cache_name}"] - except Exception as e: - Logger.warning(f"User Data:Read failure:{e}") - return [] - - -def update_anime_trailer_cache(yt_stream_links: list): - try: - yt_cache.put( - "yt_stream_links", **{f"{yt_anime_trailer_cache_name}": yt_stream_links} - ) - except Exception as e: - Logger.warning(f"User Data:Update failure:{e}") diff --git a/anixstream/Utility/utils.py b/anixstream/Utility/utils.py index e61eeb8..4f2c6b7 100644 --- a/anixstream/Utility/utils.py +++ b/anixstream/Utility/utils.py @@ -1,6 +1,7 @@ -from datetime import datetime -import shutil import os +import shutil +from datetime import datetime + # TODO: make it use color_text instead of fixed vals # from .kivy_markup_helper import color_text @@ -12,7 +13,7 @@ def write_crash(e: Exception): try: with open("crashdump.txt", "a") as file: file.write(error) - except: + except Exception: with open("crashdump.txt", "w") as file: file.write(error) return index @@ -23,9 +24,10 @@ def read_crash_file(): if not os.path.exists(crash_file_path): return None else: - with open(crash_file_path,"r") as file: + with open(crash_file_path, "r") as file: return file.read() + def move_file(source_path, dest_path): try: path = shutil.move(source_path, dest_path) diff --git a/anixstream/Utility/yaml_parser.py b/anixstream/Utility/yaml_parser.py index e1d838c..e33d325 100644 --- a/anixstream/Utility/yaml_parser.py +++ b/anixstream/Utility/yaml_parser.py @@ -1,7 +1,7 @@ -import yaml - import os +import yaml + class YamlParser: """makes managing yaml files easier""" @@ -15,7 +15,7 @@ class YamlParser: try: with open(self.file_path, "r") as yaml_file: self.data = yaml.safe_load(yaml_file) - except: + except Exception: self.data = default with open(file_path, "w") as yaml_file: yaml.dump(default, yaml_file) diff --git a/anixstream/View/AnimeScreen/anime_screen.py b/anixstream/View/AnimeScreen/anime_screen.py index 919fb3c..37ee5bf 100644 --- a/anixstream/View/AnimeScreen/anime_screen.py +++ b/anixstream/View/AnimeScreen/anime_screen.py @@ -1,17 +1,15 @@ +from kivy.properties import DictProperty, ObjectProperty, StringProperty -from kivy.properties import ObjectProperty, DictProperty, StringProperty - -from anixstream.Utility import anilist_data_helper -from anixstream.libs.anilist import AnilistBaseMediaDataSchema - -from anixstream.View.base_screen import BaseScreenView +from ...libs.anilist import AnilistBaseMediaDataSchema +from ...Utility import anilist_data_helper +from ...View.base_screen import BaseScreenView from .components import ( - AnimeHeader, - AnimeSideBar, - AnimeDescription, - AnimeReviews, - AnimeCharacters, AnimdlStreamDialog, + AnimeCharacters, + AnimeDescription, + AnimeHeader, + AnimeReviews, + AnimeSideBar, DownloadAnimeDialog, RankingsBar, ) @@ -19,6 +17,7 @@ from .components import ( class AnimeScreenView(BaseScreenView): """The anime screen view""" + caller_screen_name = StringProperty() header: AnimeHeader = ObjectProperty() side_bar: AnimeSideBar = ObjectProperty() @@ -117,12 +116,12 @@ class AnimeScreenView(BaseScreenView): self.anime_reviews.reviews = data["reviews"]["nodes"] - def stream_anime_with_custom_cmds_dialog(self,mpv=False): + def stream_anime_with_custom_cmds_dialog(self, mpv=False): """ Called when user wants to stream with custom commands """ - AnimdlStreamDialog(self.data,mpv).open() + AnimdlStreamDialog(self.data, mpv).open() def open_download_anime_dialog(self): """ diff --git a/anixstream/View/AnimeScreen/components/__init__.py b/anixstream/View/AnimeScreen/components/__init__.py index 6b58462..5d4ac98 100644 --- a/anixstream/View/AnimeScreen/components/__init__.py +++ b/anixstream/View/AnimeScreen/components/__init__.py @@ -1,10 +1,9 @@ -from .side_bar import AnimeSideBar -from .header import AnimeHeader -from .rankings_bar import RankingsBar +from .animdl_stream_dialog import AnimdlStreamDialog +from .characters import AnimeCharacters from .controls import Controls from .description import AnimeDescription -from .characters import AnimeCharacters -from .review import AnimeReviews - -from .animdl_stream_dialog import AnimdlStreamDialog from .download_anime_dialog import DownloadAnimeDialog +from .header import AnimeHeader +from .rankings_bar import RankingsBar +from .review import AnimeReviews +from .side_bar import AnimeSideBar diff --git a/anixstream/View/AnimeScreen/components/animdl_stream_dialog.py b/anixstream/View/AnimeScreen/components/animdl_stream_dialog.py index 28a68c7..51b420a 100644 --- a/anixstream/View/AnimeScreen/components/animdl_stream_dialog.py +++ b/anixstream/View/AnimeScreen/components/animdl_stream_dialog.py @@ -1,12 +1,11 @@ from kivy.clock import Clock from kivy.uix.modalview import ModalView - -from kivymd.uix.behaviors import ( - StencilBehavior, - CommonElevationBehavior, - BackgroundColorBehavior, -) from kivymd.theming import ThemableBehavior +from kivymd.uix.behaviors import ( + BackgroundColorBehavior, + CommonElevationBehavior, + StencilBehavior, +) class AnimdlStreamDialog( @@ -65,5 +64,6 @@ class AnimdlStreamDialog( app.watch_on_animdl(custom_options=cmds) self.dismiss() + def stream_anime(self, app): Clock.schedule_once(lambda _: self._stream_anime(app)) diff --git a/anixstream/View/AnimeScreen/components/characters.py b/anixstream/View/AnimeScreen/components/characters.py index fa1fd5d..8a6ba05 100644 --- a/anixstream/View/AnimeScreen/components/characters.py +++ b/anixstream/View/AnimeScreen/components/characters.py @@ -1,8 +1,9 @@ from kivy.clock import Clock -from kivy.properties import ObjectProperty, ListProperty - +from kivy.properties import ListProperty, ObjectProperty from kivymd.uix.boxlayout import MDBoxLayout +from ....Utility.anilist_data_helper import format_anilist_date_object + class AnimeCharacter(MDBoxLayout): """an Anime character data""" @@ -27,10 +28,6 @@ class AnimeCharacters(MDBoxLayout): characters = ListProperty() def update_characters_card(self, instance, characters): - format_date = lambda date_: ( - f"{date_['day']}/{date_['month']}/{date_['year']}" if date_ else "" - ) - self.container.clear_widgets() for character_ in characters: # character (character,actor) character = character_[0] @@ -40,7 +37,7 @@ class AnimeCharacters(MDBoxLayout): anime_character.character = { "name": character["name"]["full"], "gender": character["gender"], - "dateOfBirth": format_date(character["dateOfBirth"]), + "dateOfBirth": format_anilist_date_object(character["dateOfBirth"]), "image": character["image"]["medium"], "age": character["age"], "description": character["description"], diff --git a/anixstream/View/AnimeScreen/components/controls.py b/anixstream/View/AnimeScreen/components/controls.py index 80f4292..1768904 100644 --- a/anixstream/View/AnimeScreen/components/controls.py +++ b/anixstream/View/AnimeScreen/components/controls.py @@ -1,5 +1,4 @@ from kivy.properties import ObjectProperty - from kivymd.uix.gridlayout import MDGridLayout diff --git a/anixstream/View/AnimeScreen/components/description.py b/anixstream/View/AnimeScreen/components/description.py index ac289b5..a726172 100644 --- a/anixstream/View/AnimeScreen/components/description.py +++ b/anixstream/View/AnimeScreen/components/description.py @@ -1,5 +1,4 @@ from kivy.properties import StringProperty - from kivymd.uix.boxlayout import MDBoxLayout diff --git a/anixstream/View/AnimeScreen/components/download_anime_dialog.py b/anixstream/View/AnimeScreen/components/download_anime_dialog.py index 82551b7..2414318 100644 --- a/anixstream/View/AnimeScreen/components/download_anime_dialog.py +++ b/anixstream/View/AnimeScreen/components/download_anime_dialog.py @@ -1,11 +1,10 @@ from kivy.uix.modalview import ModalView - -from kivymd.uix.behaviors import ( - StencilBehavior, - CommonElevationBehavior, - BackgroundColorBehavior, -) from kivymd.theming import ThemableBehavior +from kivymd.uix.behaviors import ( + BackgroundColorBehavior, + CommonElevationBehavior, + StencilBehavior, +) # from main import AniXStreamApp diff --git a/anixstream/View/AnimeScreen/components/header.py b/anixstream/View/AnimeScreen/components/header.py index 1b381c2..9f1c290 100644 --- a/anixstream/View/AnimeScreen/components/header.py +++ b/anixstream/View/AnimeScreen/components/header.py @@ -1,5 +1,4 @@ from kivy.properties import StringProperty - from kivymd.uix.boxlayout import MDBoxLayout diff --git a/anixstream/View/AnimeScreen/components/rankings_bar.py b/anixstream/View/AnimeScreen/components/rankings_bar.py index 8f6b430..630f5ba 100644 --- a/anixstream/View/AnimeScreen/components/rankings_bar.py +++ b/anixstream/View/AnimeScreen/components/rankings_bar.py @@ -1,5 +1,4 @@ from kivy.properties import DictProperty - from kivymd.uix.boxlayout import MDBoxLayout diff --git a/anixstream/View/AnimeScreen/components/review.py b/anixstream/View/AnimeScreen/components/review.py index 5b81b83..aef77ed 100644 --- a/anixstream/View/AnimeScreen/components/review.py +++ b/anixstream/View/AnimeScreen/components/review.py @@ -1,6 +1,5 @@ -from kivy.properties import ObjectProperty, ListProperty from kivy.clock import Clock - +from kivy.properties import ListProperty, ObjectProperty from kivymd.uix.boxlayout import MDBoxLayout diff --git a/anixstream/View/AnimeScreen/components/side_bar.py b/anixstream/View/AnimeScreen/components/side_bar.py index 9994479..e733182 100644 --- a/anixstream/View/AnimeScreen/components/side_bar.py +++ b/anixstream/View/AnimeScreen/components/side_bar.py @@ -1,7 +1,6 @@ -from kivy.properties import ObjectProperty, StringProperty, DictProperty, ListProperty -from kivy.utils import get_hex_from_color from kivy.factory import Factory - +from kivy.properties import DictProperty, ListProperty, ObjectProperty, StringProperty +from kivy.utils import get_hex_from_color from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.label import MDLabel diff --git a/anixstream/View/CrashLogScreen/crashlog_screen.py b/anixstream/View/CrashLogScreen/crashlog_screen.py index c8bb092..8e19a2b 100644 --- a/anixstream/View/CrashLogScreen/crashlog_screen.py +++ b/anixstream/View/CrashLogScreen/crashlog_screen.py @@ -1,8 +1,8 @@ from kivy.properties import StringProperty -from anixstream.View.base_screen import BaseScreenView -from anixstream.Utility.utils import read_crash_file -from anixstream.Utility.kivy_markup_helper import color_text, bolden +from ...Utility.kivy_markup_helper import bolden, color_text +from ...Utility.utils import read_crash_file +from ...View.base_screen import BaseScreenView class CrashLogScreenView(BaseScreenView): diff --git a/anixstream/View/DownloadsScreen/components/task_card.py b/anixstream/View/DownloadsScreen/components/task_card.py index 726cd89..4c8a34c 100644 --- a/anixstream/View/DownloadsScreen/components/task_card.py +++ b/anixstream/View/DownloadsScreen/components/task_card.py @@ -1,12 +1,13 @@ from kivy.properties import StringProperty - from kivymd.uix.boxlayout import MDBoxLayout + # TODO: add a progress bar to show the individual progress of each task class TaskCard(MDBoxLayout): anime_task_name = StringProperty() episodes_to_download = StringProperty() - def __init__(self, anime_title:str, episodes:str, *args, **kwargs): + + def __init__(self, anime_title: str, episodes: str, *args, **kwargs): super().__init__(*args, **kwargs) self.anime_task_name = f"{anime_title}" - self.episodes_to_download = f"Episodes: {episodes}" \ No newline at end of file + self.episodes_to_download = f"Episodes: {episodes}" diff --git a/anixstream/View/DownloadsScreen/download_screen.py b/anixstream/View/DownloadsScreen/download_screen.py index f1fcfe2..6add860 100644 --- a/anixstream/View/DownloadsScreen/download_screen.py +++ b/anixstream/View/DownloadsScreen/download_screen.py @@ -1,9 +1,8 @@ from kivy.clock import Clock from kivy.properties import ObjectProperty -from kivy.logger import Logger from kivy.utils import format_bytes_to_human -from anixstream.View.base_screen import BaseScreenView +from ...View.base_screen import BaseScreenView from .components.task_card import TaskCard diff --git a/anixstream/View/HelpScreen/help_screen.py b/anixstream/View/HelpScreen/help_screen.py index 04dd83d..5bcf949 100644 --- a/anixstream/View/HelpScreen/help_screen.py +++ b/anixstream/View/HelpScreen/help_screen.py @@ -1,8 +1,8 @@ from kivy.properties import ObjectProperty, StringProperty -from anixstream.View.base_screen import BaseScreenView -from anixstream.Utility.kivy_markup_helper import bolden, color_text, underline -from anixstream.Utility.data import themes_available +from ...Utility.data import themes_available +from ...Utility.kivy_markup_helper import bolden, color_text, underline +from ...View.base_screen import BaseScreenView class HelpScreenView(BaseScreenView): diff --git a/anixstream/View/HomeScreen/home_screen.py b/anixstream/View/HomeScreen/home_screen.py index 41927e7..f346b7f 100644 --- a/anixstream/View/HomeScreen/home_screen.py +++ b/anixstream/View/HomeScreen/home_screen.py @@ -1,6 +1,6 @@ from kivy.properties import ObjectProperty -from anixstream.View.base_screen import BaseScreenView +from ...View.base_screen import BaseScreenView class HomeScreenView(BaseScreenView): diff --git a/anixstream/View/MylistScreen/my_list_screen.py b/anixstream/View/MylistScreen/my_list_screen.py index 431adba..8ab430b 100644 --- a/anixstream/View/MylistScreen/my_list_screen.py +++ b/anixstream/View/MylistScreen/my_list_screen.py @@ -1,7 +1,7 @@ -from kivy.properties import ObjectProperty, StringProperty, DictProperty from kivy.clock import Clock +from kivy.properties import DictProperty, ObjectProperty, StringProperty -from anixstream.View.base_screen import BaseScreenView +from ...View.base_screen import BaseScreenView class MyListScreenView(BaseScreenView): diff --git a/anixstream/View/SearchScreen/components/filters.py b/anixstream/View/SearchScreen/components/filters.py index 52cffeb..5fff5a3 100644 --- a/anixstream/View/SearchScreen/components/filters.py +++ b/anixstream/View/SearchScreen/components/filters.py @@ -1,5 +1,4 @@ -from kivy.properties import StringProperty, DictProperty - +from kivy.properties import DictProperty, StringProperty from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.dropdownitem import MDDropDownItem from kivymd.uix.menu import MDDropdownMenu diff --git a/anixstream/View/SearchScreen/components/pagination.py b/anixstream/View/SearchScreen/components/pagination.py index 8fee429..7b6a6bc 100644 --- a/anixstream/View/SearchScreen/components/pagination.py +++ b/anixstream/View/SearchScreen/components/pagination.py @@ -1,5 +1,4 @@ -from kivy.properties import ObjectProperty, NumericProperty - +from kivy.properties import NumericProperty, ObjectProperty from kivymd.uix.boxlayout import MDBoxLayout diff --git a/anixstream/View/SearchScreen/search_screen.py b/anixstream/View/SearchScreen/search_screen.py index c906b15..ef17cc2 100644 --- a/anixstream/View/SearchScreen/search_screen.py +++ b/anixstream/View/SearchScreen/search_screen.py @@ -1,8 +1,9 @@ -from kivy.properties import ObjectProperty, StringProperty from kivy.clock import Clock +from kivy.properties import ObjectProperty, StringProperty -from anixstream.View.base_screen import BaseScreenView -from .components import TrendingAnimeSideBar, Filters, SearchResultsPagination +from ...View.base_screen import BaseScreenView + +from .components import Filters, SearchResultsPagination, TrendingAnimeSideBar class SearchScreenView(BaseScreenView): diff --git a/anixstream/View/__init__.py b/anixstream/View/__init__.py index cefdc32..7bcbea3 100644 --- a/anixstream/View/__init__.py +++ b/anixstream/View/__init__.py @@ -1,8 +1,8 @@ # screens -from .HomeScreen.home_screen import HomeScreenView -from .SearchScreen.search_screen import SearchScreenView -from .MylistScreen.my_list_screen import MyListScreenView from .AnimeScreen.anime_screen import AnimeScreenView from .CrashLogScreen.crashlog_screen import CrashLogScreenView from .DownloadsScreen.download_screen import DownloadsScreenView from .HelpScreen.help_screen import HelpScreenView +from .HomeScreen.home_screen import HomeScreenView +from .MylistScreen.my_list_screen import MyListScreenView +from .SearchScreen.search_screen import SearchScreenView diff --git a/anixstream/View/base_screen.py b/anixstream/View/base_screen.py index 3c9bdab..477909e 100644 --- a/anixstream/View/base_screen.py +++ b/anixstream/View/base_screen.py @@ -1,13 +1,12 @@ from kivy.properties import ObjectProperty, StringProperty - from kivymd.app import MDApp -from kivymd.uix.screen import MDScreen -from kivymd.uix.navigationrail import MDNavigationRail, MDNavigationRailItem from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.button import MDIconButton +from kivymd.uix.navigationrail import MDNavigationRail, MDNavigationRailItem +from kivymd.uix.screen import MDScreen from kivymd.uix.tooltip import MDTooltip -from anixstream.Utility.observer import Observer +from ..Utility.observer import Observer class NavRail(MDNavigationRail): diff --git a/anixstream/View/components/animdl_dialog/animdl_dialog.py b/anixstream/View/components/animdl_dialog/animdl_dialog.py index f7ce40c..a20400f 100644 --- a/anixstream/View/components/animdl_dialog/animdl_dialog.py +++ b/anixstream/View/components/animdl_dialog/animdl_dialog.py @@ -1,4 +1,5 @@ from kivy.uix.modalview import ModalView + class AnimdlDialogPopup(ModalView): - pass \ No newline at end of file + pass diff --git a/anixstream/View/components/media_card/components/media_player.py b/anixstream/View/components/media_card/components/media_player.py index 4e19821..28b8100 100644 --- a/anixstream/View/components/media_card/components/media_player.py +++ b/anixstream/View/components/media_card/components/media_player.py @@ -1,10 +1,11 @@ from kivy.uix.videoplayer import VideoPlayer + # TODO: make fullscreen exp better class MediaPopupVideoPlayer(VideoPlayer): def __init__(self, **kwargs): super().__init__(**kwargs) - + def on_fullscreen(self, instance, value): super().on_fullscreen(instance, value) - # self.state = "pause" \ No newline at end of file + # self.state = "pause" diff --git a/anixstream/View/components/media_card/components/media_popup.py b/anixstream/View/components/media_card/components/media_popup.py index edfe639..3ce2794 100644 --- a/anixstream/View/components/media_card/components/media_popup.py +++ b/anixstream/View/components/media_card/components/media_popup.py @@ -1,14 +1,14 @@ -from kivy.properties import ObjectProperty -from kivy.clock import Clock from kivy.animation import Animation +from kivy.clock import Clock +from kivy.properties import ObjectProperty from kivy.uix.modalview import ModalView - +from kivy.utils import QueryDict from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import ( BackgroundColorBehavior, - StencilBehavior, CommonElevationBehavior, HoverBehavior, + StencilBehavior, ) @@ -20,14 +20,47 @@ class MediaPopup( BackgroundColorBehavior, ModalView, ): - caller = ObjectProperty() + caller = ObjectProperty( + QueryDict( + { + "anime_id": "", + "title": "", + "is_play": "", + "trailer_url": "", + "episodes": "", + "favourites": "", + "popularity": "", + "media_status": "", + "is_in_my_list": False, + "is_in_my_notify": False, + "genres": "", + "first_aired_on": "", + "description": "", + "tags": "", + "studios": "", + "next_airing_episode": "", + "producers": "", + "stars": [0, 0, 0, 0, 0, 0], + "cover_image_url": "", + "preview_image": "", + "has_trailer_color": [0, 0, 0, 0], + } + ) + ) + player = ObjectProperty() - def __init__(self, caller=None, *args, **kwarg): - self.caller = caller + def __init__(self, *args, **kwarg): + # self.caller = caller super(MediaPopup, self).__init__(*args, **kwarg) self.player.bind(fullscreen=self.handle_clean_fullscreen_transition) + def update_caller(self, caller): + self.caller = caller + + def on_caller(self, *args): + Clock.schedule_once(lambda _: self.apply_class_lang_rules(), -1) + def open(self, *_args, **kwargs): """Display the modal in the Window. @@ -61,7 +94,6 @@ class MediaPopup( super().open(*_args, **kwargs) def _align_center(self, *_args): - if self.caller: if self._is_open: self.center = self.caller.to_window(*self.caller.center) @@ -83,13 +115,10 @@ class MediaPopup( if not fullscreen: if not self._is_open: instance.state = "stop" - # if vid := instance._video: - # vid.unload() + if vid := instance._video: + vid.unload() else: instance.state = "stop" - # if vid := instance._video: - # vid.unload() + if vid := instance._video: + vid.unload() self.dismiss() - - -media_card_popup = MediaPopup() diff --git a/anixstream/View/components/media_card/media_card.kv b/anixstream/View/components/media_card/media_card.kv index e930ff9..e2091a8 100644 --- a/anixstream/View/components/media_card/media_card.kv +++ b/anixstream/View/components/media_card/media_card.kv @@ -13,7 +13,7 @@ width: dp(100) height: dp(150) MDDivider: - color:root.has_trailer_color + color:root.theme_cls.primaryColor if root._trailer_url else [0.5, 0.5, 0.5, 0.5] SingleLineLabel: font_style:"Label" role:"medium" diff --git a/anixstream/View/components/media_card/media_card.py b/anixstream/View/components/media_card/media_card.py index b8194f5..58c4724 100644 --- a/anixstream/View/components/media_card/media_card.py +++ b/anixstream/View/components/media_card/media_card.py @@ -1,17 +1,17 @@ +from kivy.clock import Clock from kivy.properties import ( - ObjectProperty, - StringProperty, BooleanProperty, ListProperty, NumericProperty, + ObjectProperty, + StringProperty, ) -from kivy.clock import Clock from kivy.uix.behaviors import ButtonBehavior - -from kivymd.uix.boxlayout import MDBoxLayout +from kivymd.app import MDApp from kivymd.uix.behaviors import HoverBehavior +from kivymd.uix.boxlayout import MDBoxLayout -from .components.media_popup import media_card_popup, MediaPopup +from .components.media_popup import MediaPopup class MediaCard(ButtonBehavior, HoverBehavior, MDBoxLayout): @@ -20,6 +20,7 @@ class MediaCard(ButtonBehavior, HoverBehavior, MDBoxLayout): title = StringProperty() is_play = ObjectProperty() trailer_url = StringProperty() + _trailer_url: str | None = StringProperty() episodes = StringProperty() favourites = StringProperty() popularity = StringProperty() @@ -42,6 +43,8 @@ class MediaCard(ButtonBehavior, HoverBehavior, MDBoxLayout): super().__init__(**kwargs) self.orientation = "vertical" + self.app: MDApp | None = MDApp.get_running_app() + if trailer_url: self.trailer_url = trailer_url self.adaptive_size = True @@ -63,21 +66,38 @@ class MediaCard(ButtonBehavior, HoverBehavior, MDBoxLayout): def on_dismiss(self, popup: MediaPopup): popup.player.state = "stop" - # if popup.player._video: - # popup.player._video.unload() + if popup.player._video: + popup.player._video.unload() def set_preview_image(self, image): self.preview_image = image def set_trailer_url(self, trailer_url): self.trailer_url = trailer_url - self.has_trailer_color = self.theme_cls.primaryColor def open(self, *_): - media_card_popup.caller = self - media_card_popup.title = self.title - media_card_popup.bind(on_dismiss=self.on_dismiss, on_open=self.on_popup_open) - media_card_popup.open(self) + if app := self.app: + popup: MediaPopup = app.media_card_popup + # self.popup.caller = self + popup.update_caller(self) + popup.title = self.title + popup.bind(on_dismiss=self.on_dismiss, on_open=self.on_popup_open) + popup.open(self) + + # trailer stuff + from ....Utility.media_card_loader import media_card_loader + + if trailer := self._trailer_url: + # from ....Utility import show_notification + + # TODO: show an indefinate progress while traile is still not available + # show_notification("Pytube", "Please wait for trailer to load") + if trailer_url := media_card_loader.get_trailer_from_pytube( + trailer, self.title + ): + self.trailer_url = trailer_url + else: + self._trailer_url = None # ---------------respond to user actions and call appropriate model------------------------- def on_is_in_my_list(self, instance, in_user_anime_list): diff --git a/anixstream/View/screens.py b/anixstream/View/screens.py index 925ac16..23f645b 100644 --- a/anixstream/View/screens.py +++ b/anixstream/View/screens.py @@ -1,23 +1,22 @@ -from anixstream.Controller import ( - SearchScreenController, - HomeScreenController, - MyListScreenController, +from ..Controller import ( AnimeScreenController, + CrashLogScreenController, DownloadsScreenController, HelpScreenController, - CrashLogScreenController, + HomeScreenController, + MyListScreenController, + SearchScreenController, ) -from anixstream.Model import ( - HomeScreenModel, - SearchScreenModel, - MyListScreenModel, +from ..Model import ( AnimeScreenModel, + CrashLogScreenModel, DownloadsScreenModel, HelpScreenModel, - CrashLogScreenModel, + HomeScreenModel, + MyListScreenModel, + SearchScreenModel, ) - screens = { "home screen": { "model": HomeScreenModel, diff --git a/anixstream/__main__.py b/anixstream/__main__.py index 02fe977..e1d06a5 100644 --- a/anixstream/__main__.py +++ b/anixstream/__main__.py @@ -1,83 +1,68 @@ import os import random -os.environ["KIVY_VIDEO"] = "ffpyplayer" - -from queue import Queue -from threading import Thread -from subprocess import Popen import webbrowser +from queue import Queue +from subprocess import Popen +from threading import Thread import plyer - +from kivy.clock import Clock from kivy.config import Config -from kivy.resources import resource_find,resource_add_path,resource_remove_path +from kivy.loader import Loader +from kivy.logger import Logger +from kivy.resources import resource_add_path, resource_find, resource_remove_path +from kivy.uix.screenmanager import FadeTransition, ScreenManager +from kivy.uix.settings import Settings, SettingsWithSidebar +from kivymd.app import MDApp + +from .libs.animdl import AnimdlApi +from .Utility import ( + animdl_config_manager, + show_notification, + themes_available, + user_data_helper, +) +from .Utility.utils import write_crash +from .View.components.media_card.components.media_popup import MediaPopup +from .View.screens import screens + +os.environ["KIVY_VIDEO"] = "ffpyplayer" # noqa: E402 + +Config.set("graphics", "width", "1000") # noqa: E402 +Config.set("graphics", "minimum_width", "1000") # noqa: E402 +Config.set("kivy", "window_icon", resource_find("logo.ico")) # noqa: E402 +Config.write() # noqa: E402 # resource_add_path("_internal") app_dir = os.path.dirname(__file__) -# test - -# test_end - - # make sure we aint searching dist folder -dist_folder = os.path.join(app_dir,"dist") +dist_folder = os.path.join(app_dir, "dist") resource_remove_path(dist_folder) -assets_folder = os.path.join(app_dir,"assets") +assets_folder = os.path.join(app_dir, "assets") resource_add_path(assets_folder) -conigs_folder = os.path.join(app_dir,"configs") +conigs_folder = os.path.join(app_dir, "configs") resource_add_path(conigs_folder) -data_folder = os.path.join(app_dir,"data") -resource_add_path(data_folder) - -Config.set("graphics","width","1000") -Config.set("graphics","minimum_width","1000") - -Config.set('kivy', 'window_icon', resource_find("logo.ico")) -Config.write() - # from kivy.core.window import Window - -from kivy.loader import Loader - Loader.num_workers = 5 Loader.max_upload_per_frame = 10 -from kivy.clock import Clock -from kivy.logger import Logger -from kivy.uix.screenmanager import ScreenManager, FadeTransition -from kivy.uix.settings import SettingsWithSidebar, Settings - -from kivymd.icon_definitions import md_icons -from kivymd.app import MDApp - -from anixstream.View.screens import screens -from anixstream.libs.animdl import AnimdlApi -from anixstream.Utility import ( - themes_available, - show_notification, - user_data_helper, - animdl_config_manager, -) -from anixstream.Utility.utils import write_crash # Ensure the user data fields exist if not (user_data_helper.user_data.exists("user_anime_list")): user_data_helper.update_user_anime_list([]) -if not (user_data_helper.yt_cache.exists("yt_stream_links")): - user_data_helper.update_anime_trailer_cache([]) # TODO: Confirm data integrity from user_data and yt_cache - - class AniXStreamApp(MDApp): + # some initialize + queue = Queue() downloads_queue = Queue() animdl_streaming_subprocess: Popen | None = None - default_anime_image = resource_find(random.choice(["default_1.jpg","default.jpg"])) - default_banner_image = resource_find(random.choice(["banner_1.jpg","banner.jpg"])) + default_anime_image = resource_find(random.choice(["default_1.jpg", "default.jpg"])) + default_banner_image = resource_find(random.choice(["banner_1.jpg", "banner.jpg"])) # default_video = resource_find("Billyhan_When you cant afford Crunchyroll to watch anime.mp4") def worker(self, queue: Queue): @@ -140,8 +125,11 @@ class AniXStreamApp(MDApp): return self.manager_screens def on_start(self, *args): - if self.config.get("Preferences","is_startup_anime_enable")=="1": # type: ignore - Clock.schedule_once(lambda _:self.home_screen.controller.populate_home_screen(),1) + self.media_card_popup = MediaPopup() + if self.config.get("Preferences", "is_startup_anime_enable") == "1": # type: ignore + Clock.schedule_once( + lambda _: self.home_screen.controller.populate_home_screen(), 1 + ) def generate_application_screens(self) -> None: for i, name_screen in enumerate(screens.keys()): @@ -153,7 +141,6 @@ class AniXStreamApp(MDApp): self.manager_screens.add_widget(view) def build_config(self, config): - # General settings setup if vid_path := plyer.storagepath.get_videos_dir(): # type: ignore downloads_dir = os.path.join(vid_path, "anixstream") @@ -278,38 +265,44 @@ class AniXStreamApp(MDApp): self.add_anime_to_user_downloads_list(anime_id) # TODO:Add custom download cmds functionality - on_progress = lambda *args: self.download_screen.on_episode_download_progress( - *args - ) + def on_progress(*args): + return self.download_screen.on_episode_download_progress(*args) + output_path = self.config.get("Preferences", "downloads_dir") # type: ignore self.download_screen.on_new_download_task( default_cmds["title"], default_cmds.get("episodes_range") ) if episodes_range := default_cmds.get("episodes_range"): - download_task = lambda: AnimdlApi.download_anime_by_title( - default_cmds["title"], - on_progress, - lambda anime_title, episode: show_notification( - "Finished installing an episode", f"{anime_title}-{episode}" - ), - self.download_anime_complete, - output_path, - episodes_range, - ) # ,default_cmds["quality"] + + def download_task(): + return AnimdlApi.download_anime_by_title( + default_cmds["title"], + on_progress, + lambda anime_title, episode: show_notification( + "Finished installing an episode", f"{anime_title}-{episode}" + ), + self.download_anime_complete, + output_path, + episodes_range, + ) # ,default_cmds["quality"] + self.downloads_queue.put(download_task) Logger.info( f"Downloader:Successfully Queued {default_cmds['title']} for downloading" ) else: - download_task = lambda: AnimdlApi.download_anime_by_title( - default_cmds["title"], - on_progress, - lambda anime_title, episode: show_notification( - "Finished installing an episode", f"{anime_title}-{episode}" - ), - self.download_anime_complete, - output_path, - ) # ,default_cmds.get("quality") + + def download_task(): + return AnimdlApi.download_anime_by_title( + default_cmds["title"], + on_progress, + lambda anime_title, episode: show_notification( + "Finished installing an episode", f"{anime_title}-{episode}" + ), + self.download_anime_complete, + output_path, + ) # ,default_cmds.get("quality") + self.downloads_queue.put(download_task) Logger.info( f"Downloader:Successfully Queued {default_cmds['title']} for downloading" @@ -333,6 +326,7 @@ class AniXStreamApp(MDApp): show_notification( "Failure", f"Failed to open {title_} in browser on allanime site" ) + return if webbrowser.open(parsed_link): Logger.info( f"AniXStream:Successfully opened {title} in browser allanime site" @@ -348,8 +342,8 @@ class AniXStreamApp(MDApp): "Failure", f"Failed to open {title} in browser on allanime site" ) except Exception as e: - show_notification("Something went wrong",f"{e}") - + show_notification("Something went wrong", f"{e}") + def stream_anime_with_custom_input_cmds(self, *cmds): self.animdl_streaming_subprocess = ( AnimdlApi._run_animdl_command_and_get_subprocess(["stream", *cmds]) @@ -371,8 +365,8 @@ class AniXStreamApp(MDApp): # TODO: End mpv child process properly for stream in streams: try: - if isinstance(stream,str): - show_notification("Failed to stream current episode",f"{stream}") + if isinstance(stream, str): + show_notification("Failed to stream current episode", f"{stream}") continue self.animdl_streaming_subprocess = stream @@ -384,7 +378,7 @@ class AniXStreamApp(MDApp): del stream return except Exception as e: - show_notification("Something went wrong while streaming",f"{e}") + show_notification("Something went wrong while streaming", f"{e}") def watch_on_animdl( self, @@ -407,20 +401,24 @@ class AniXStreamApp(MDApp): self.stop_streaming = True if stream_with_mpv_options: - stream_func = lambda: self.stream_anime_with_mpv( - stream_with_mpv_options["title"], - stream_with_mpv_options.get("episodes_range"), - stream_with_mpv_options["quality"], - ) + + def stream_func(): + return self.stream_anime_with_mpv( + stream_with_mpv_options["title"], + stream_with_mpv_options.get("episodes_range"), + stream_with_mpv_options["quality"], + ) + self.queue.put(stream_func) Logger.info( f"Animdl:Successfully started to stream {stream_with_mpv_options['title']}" ) else: - stream_func = lambda: self.stream_anime_with_custom_input_cmds( - *custom_options - ) + + def stream_func(): + return self.stream_anime_with_custom_input_cmds(*custom_options) + self.queue.put(stream_func) show_notification("Streamer", "Started streaming") @@ -428,8 +426,14 @@ class AniXStreamApp(MDApp): def run_app(): AniXStreamApp().run() + if __name__ == "__main__": - try: + in_development = True + + if in_development: run_app() - except Exception as e: - write_crash(e) + else: + try: + run_app() + except Exception as e: + write_crash(e) diff --git a/anixstream/libs/anilist/__init__.py b/anixstream/libs/anilist/__init__.py index f8023aa..1fe87ca 100644 --- a/anixstream/libs/anilist/__init__.py +++ b/anixstream/libs/anilist/__init__.py @@ -1,6 +1,6 @@ """ -This module contains an abstraction for interaction with the anilist api making it easy and efficient +his module contains an abstraction for interaction with the anilist api making it easy and efficient """ from .anilist import AniList -from .anilist_data_schema import AnilistBaseMediaDataSchema \ No newline at end of file +from .anilist_data_schema import AnilistBaseMediaDataSchema diff --git a/anixstream/libs/anilist/anilist.py b/anixstream/libs/anilist/anilist.py index 7790abc..2681ad7 100644 --- a/anixstream/libs/anilist/anilist.py +++ b/anixstream/libs/anilist/anilist.py @@ -1,6 +1,7 @@ """ This is the core module availing all the abstractions of the anilist api """ + import requests from .queries_graphql import ( @@ -15,17 +16,21 @@ from .queries_graphql import ( anime_relations_query, airing_schedule_query, upcoming_anime_query, - anime_query - ) + anime_query, +) from .anilist_data_schema import AnilistDataSchema # from kivy.network.urlrequest import UrlRequestRequests + class AniList: """ - This class provides an abstraction for the anilist api + This class provides an abstraction for the anilist api """ + @classmethod - def get_data(cls,query:str,variables:dict = {})->tuple[bool,AnilistDataSchema]: + def get_data( + cls, query: str, variables: dict = {} + ) -> tuple[bool, AnilistDataSchema]: """ The core abstraction for getting data from the anilist api @@ -39,58 +44,69 @@ class AniList: url = "https://graphql.anilist.co" # req=UrlRequestRequests(url, cls.got_data,) try: - # TODO: check if data is as expected - response = requests.post(url,json={"query":query,"variables":variables},timeout=5) - anilist_data:AnilistDataSchema = response.json() - return (True,anilist_data) + # TODO: check if data is as expected + response = requests.post( + url, json={"query": query, "variables": variables}, timeout=5 + ) + anilist_data: AnilistDataSchema = response.json() + return (True, anilist_data) except requests.exceptions.Timeout: - return (False,{"Error":"Timeout Exceeded for connection there might be a problem with your internet or anilist is down."}) # type: ignore + return ( + False, + { + "Error": "Timeout Exceeded for connection there might be a problem with your internet or anilist is down." + }, + ) # type: ignore except requests.exceptions.ConnectionError: - return (False,{"Error":"There might be a problem with your internet or anilist is down."}) # type: ignore + return ( + False, + { + "Error": "There might be a problem with your internet or anilist is down." + }, + ) # type: ignore except Exception as e: - return (False,{"Error":f"{e}"}) # type: ignore + return (False, {"Error": f"{e}"}) # type: ignore @classmethod - def search(cls, - query:str|None=None, - sort:list[str]|None=None, - genre_in:list[str]|None=None, - id_in:list[int]|None=None, - genre_not_in:list[str]=["hentai"], - popularity_greater:int|None=None, - popularity_lesser:int|None=None, - averageScore_greater:int|None=None, - averageScore_lesser:int|None=None, - tag_in:list[str]|None=None, - tag_not_in:list[str]|None=None, - status:str|None = None, - status_in:list[str]|None=None, - status_not_in:list[str]|None=None, - endDate_greater:int|None=None, - endDate_lesser:int|None=None, - start_greater:int|None=None, - start_lesser:int|None=None, - page:int|None=None - ): + def search( + cls, + query: str | None = None, + sort: str | None = None, + genre_in: list[str] | None = None, + id_in: list[int] | None = None, + genre_not_in: list[str] = ["hentai"], + popularity_greater: int | None = None, + popularity_lesser: int | None = None, + averageScore_greater: int | None = None, + averageScore_lesser: int | None = None, + tag_in: list[str] | None = None, + tag_not_in: list[str] | None = None, + status: str | None = None, + status_in: list[str] | None = None, + status_not_in: list[str] | None = None, + endDate_greater: int | None = None, + endDate_lesser: int | None = None, + start_greater: int | None = None, + start_lesser: int | None = None, + page: int | None = None, + ): """ A powerful method for searching anime using the anilist api availing most of its options """ - variables = {} + variables = {} for key, val in list(locals().items())[1:]: if val is not None and key not in ["variables"]: variables[key] = val - search_results = cls.get_data(search_query,variables=variables) + search_results = cls.get_data(search_query, variables=variables) return search_results @classmethod - def get_anime(cls,id:int): + def get_anime(cls, id: int): """ Gets a single anime by a valid anilist anime id """ - variables = { - "id":id - } - return cls.get_data(anime_query,variables) + variables = {"id": id} + return cls.get_data(anime_query, variables) @classmethod def get_trending(cls): @@ -131,38 +147,36 @@ class AniList: """ most_popular = cls.get_data(most_popular_query) return most_popular - + # FIXME:dont know why its not giving useful data @classmethod - def get_recommended_anime_for(cls,id:int): + def get_recommended_anime_for(cls, id: int): recommended_anime = cls.get_data(recommended_query) return recommended_anime - + @classmethod - def get_charcters_of(cls,id:int): - variables = {"id":id} - characters = cls.get_data(anime_characters_query,variables) + def get_charcters_of(cls, id: int): + variables = {"id": id} + characters = cls.get_data(anime_characters_query, variables) return characters - - + @classmethod - def get_related_anime_for(cls,id:int): - variables = {"id":id} - related_anime = cls.get_data(anime_relations_query,variables) - return related_anime - + def get_related_anime_for(cls, id: int): + variables = {"id": id} + related_anime = cls.get_data(anime_relations_query, variables) + return related_anime + @classmethod - def get_airing_schedule_for(cls,id:int): - variables = {"id":id} - airing_schedule = cls.get_data(airing_schedule_query,variables) + def get_airing_schedule_for(cls, id: int): + variables = {"id": id} + airing_schedule = cls.get_data(airing_schedule_query, variables) return airing_schedule - + @classmethod - def get_upcoming_anime(cls,page:int): + def get_upcoming_anime(cls, page: int): """ Gets upcoming anime from anilist """ - variables = {"page":page} - upcoming_anime = cls.get_data(upcoming_anime_query,variables) + variables = {"page": page} + upcoming_anime = cls.get_data(upcoming_anime_query, variables) return upcoming_anime - diff --git a/anixstream/libs/anilist/anilist_data_schema.py b/anixstream/libs/anilist/anilist_data_schema.py index 28a1b26..33eb3a7 100644 --- a/anixstream/libs/anilist/anilist_data_schema.py +++ b/anixstream/libs/anilist/anilist_data_schema.py @@ -1,156 +1,163 @@ """ This module defines the shape of the anilist data that can be received in order to enhance dev experience """ + from typing import TypedDict + class AnilistMediaTitle(TypedDict): - english:str - romaji:str + english: str + romaji: str class AnilistImage(TypedDict): - medium:str - extraLarge:str - small:str - large:str - - + medium: str + extraLarge: str + small: str + large: str class AnilistMediaTrailer(TypedDict): - id:str - site:str + id: str + site: str class AnilistStudio(TypedDict): - name:str - favourites:int - isAnimationStudio:bool + name: str + favourites: int + isAnimationStudio: bool class AnilistStudioNodes(TypedDict): - nodes:list[AnilistStudio] + nodes: list[AnilistStudio] class AnilistMediaTag(TypedDict): - name:str - rank:int + name: str + rank: int class AnilistDateObject(TypedDict): - day:int - month:int - year:int + day: int + month: int + year: int class AnilistMediaNextAiringEpisode(TypedDict): - timeUntilAiring:int - airingAt:int - episode:int - + timeUntilAiring: int + airingAt: int + episode: int class AnilistUser(TypedDict): - name:str - avatar:AnilistImage + name: str + avatar: AnilistImage class AnilistReview(TypedDict): - summary:str - user:AnilistUser + summary: str + user: AnilistUser + class AnilistReviewNodes(TypedDict): - nodes:list[AnilistReview] + nodes: list[AnilistReview] + class AnilistMediaRanking(TypedDict): - rank:int - context:str + rank: int + context: str class AnilistExternalLink(TypedDict): - url:str - site:str - icon:str + url: str + site: str + icon: str class AnilistName(TypedDict): - full:str + full: str class AnilistCharacter(TypedDict): - name:AnilistName - gender:str|None - dateOfBirth:AnilistDateObject|None - age:int - image:AnilistImage - description:str + name: AnilistName + gender: str | None + dateOfBirth: AnilistDateObject | None + age: int + image: AnilistImage + description: str + # class AnilistCharacterNode(TypedDict): # node + class AnilistVoiceActor(TypedDict): - name:AnilistName - image:AnilistImage + name: AnilistName + image: AnilistImage class AnilistCharactersEdge(TypedDict): - node:list[AnilistCharacter] - voiceActors:list[AnilistVoiceActor] + node: list[AnilistCharacter] + voiceActors: list[AnilistVoiceActor] + class AnilistCharactersEdges(TypedDict): - edges:list[AnilistCharactersEdge] + edges: list[AnilistCharactersEdge] + class AnilistBaseMediaDataSchema(TypedDict): """ This a convenience class is used to type the received Anilist data to enhance dev experience """ - id:str - title:AnilistMediaTitle - coverImage:AnilistImage - trailer:AnilistMediaTrailer|None - popularity:int - favourites:int - averageScore:int - genres:list[str] - episodes:int|None - description:str|None - studios:AnilistStudioNodes - tags:list[AnilistMediaTag] - startDate:AnilistDateObject - endDate:AnilistDateObject - status:str - nextAiringEpisode:AnilistMediaNextAiringEpisode - season:str - seasonYear:int - duration:int - synonyms:list[str] - countryOfOrigin:str - source:str - hashtag:str|None - siteUrl:str - reviews:AnilistReviewNodes - bannerImage:str|None - rankings:list[AnilistMediaRanking] - externalLinks:list[AnilistExternalLink] - characters:AnilistCharactersEdges - format:str + + id: str + title: AnilistMediaTitle + coverImage: AnilistImage + trailer: AnilistMediaTrailer | None + popularity: int + favourites: int + averageScore: int + genres: list[str] + episodes: int | None + description: str | None + studios: AnilistStudioNodes + tags: list[AnilistMediaTag] + startDate: AnilistDateObject + endDate: AnilistDateObject + status: str + nextAiringEpisode: AnilistMediaNextAiringEpisode + season: str + seasonYear: int + duration: int + synonyms: list[str] + countryOfOrigin: str + source: str + hashtag: str | None + siteUrl: str + reviews: AnilistReviewNodes + bannerImage: str | None + rankings: list[AnilistMediaRanking] + externalLinks: list[AnilistExternalLink] + characters: AnilistCharactersEdges + format: str class AnilistPageInfo(TypedDict): - total:int - perPage:int - currentPage:int - hasNextPage:bool + total: int + perPage: int + currentPage: int + hasNextPage: bool class AnilistPage(TypedDict): - media:list[AnilistBaseMediaDataSchema] - pageInfo:AnilistPageInfo + media: list[AnilistBaseMediaDataSchema] + pageInfo: AnilistPageInfo class AnilistPages(TypedDict): Page: AnilistPage + class AnilistDataSchema(TypedDict): - data:AnilistPages - Error:str + data: AnilistPages + Error: str diff --git a/anixstream/libs/animdl/animdl_api.py b/anixstream/libs/animdl/animdl_api.py index 341e53a..f8b14d9 100644 --- a/anixstream/libs/animdl/animdl_api.py +++ b/anixstream/libs/animdl/animdl_api.py @@ -1,35 +1,32 @@ import os -import time import re import shutil -from subprocess import Popen, run, PIPE, CompletedProcess +from subprocess import PIPE, CompletedProcess, Popen, run from typing import Callable -from .extras import Logger from .animdl_data_helper import ( + anime_title_percentage_match, filter_broken_streams, filter_streams_by_quality, + parse_stream_urls_data, path_parser, search_output_parser, - anime_title_percentage_match, - parse_stream_urls_data, ) from .animdl_exceptions import ( AnimdlAnimeUrlNotFoundException, - InvalidAnimdlCommandsException, MPVNotFoundException, NoValidAnimeStreamsException, Python310NotFoundException, ) from .animdl_types import AnimdlAnimeEpisode, AnimdlAnimeUrlAndTitle, AnimdlData - +from .extras import Logger broken_link_pattern = r"https://tools.fast4speed.rsvp/\w*" def run_mpv_command(*cmds) -> Popen: if mpv := shutil.which("mpv"): - Logger.info({"Animdl Api: Started mpv command"}) + Logger.debug({"Animdl Api: Started mpv command"}) child_process = Popen( [mpv, *cmds], stderr=PIPE, @@ -58,7 +55,7 @@ class AnimdlApi: CompletedProcess: the completed animdl process """ if py_path := shutil.which("python"): - Logger.info("Animdl Api: Started Animdl command") + Logger.debug("Animdl Api: Started Animdl command") if capture: return run( [py_path, "-m", "animdl", *cmds], @@ -91,7 +88,7 @@ class AnimdlApi: parsed_cmds = list(cmds) if py_path := shutil.which("python"): - Logger.info("Animdl Api: Started Animdl command") + Logger.debug("Animdl Api: Started Animdl command") base_cmds = [py_path, "-m", "animdl"] cmds_ = [*base_cmds, *parsed_cmds] child_process = Popen(cmds_) @@ -190,15 +187,15 @@ class AnimdlApi: subs = ";".join(subtitles) cmds = [*cmds, f"--sub-files={subs}"] - Logger.info( + Logger.debug( f"Animdl Api Mpv Streamer: Starting to stream on mpv with commands: {cmds}" ) yield run_mpv_command(*cmds) - Logger.info( + Logger.debug( f"Animdl Api Mpv Streamer: Finished to stream episode {episode['episode']} on mpv" ) else: - Logger.info( + Logger.debug( f"Animdl Api Mpv Streamer: Failed to stream episode {episode['episode']} no valid streams" ) yield f"Epiosde {episode['episode']} doesnt have any valid stream links" @@ -299,7 +296,7 @@ class AnimdlApi: if not os.path.exists(download_location): os.mkdir(download_location) - Logger.info(f"Animdl Api Downloader: Started downloading: {anime_title}") + Logger.debug(f"Animdl Api Downloader: Started downloading: {anime_title}") for episode in anime_streams_data.episodes: episode_number = episode["episode"] episode_title = f"Episode {episode_number}" @@ -338,7 +335,7 @@ class AnimdlApi: # check if its adaptive or progressive and call the appropriate downloader if stream_url and subtitles and audio_tracks: - Logger.info( + Logger.debug( f"Animdl api Downloader: Downloading adaptive episode {anime_title}-{episode_title}" ) cls.download_adaptive( @@ -351,8 +348,8 @@ class AnimdlApi: ) elif stream_url and subtitles: # probably wont occur - Logger.info( - f"Animdl api Downloader: downloading ? episode {anime_title}-{episode_title}" + Logger.debug( + f"Animdl api Downloader: downloading !? episode {anime_title}-{episode_title}" ) cls.download_video_and_subtitles( stream_url, @@ -362,7 +359,7 @@ class AnimdlApi: episode_info, ) else: - Logger.info( + Logger.debug( f"Animdl api Downloader: Downloading progressive episode {anime_title}-{episode_title}" ) cls.download_progressive( @@ -375,16 +372,16 @@ class AnimdlApi: # epiosode download complete on_episode_download_complete(anime_title, episode_title) successful_downloads.append(episode_number) - Logger.info( + Logger.debug( f"Animdl api Downloader: Success in dowloading {anime_title}-{episode_title}" ) except Exception as e: - Logger.info( + Logger.debug( f"Animdl api Downloader: Failed in dowloading {anime_title}-{episode_title}; reason {e}" ) failed_downloads.append(episode_number) - Logger.info( + Logger.debug( f"Animdl api Downloader: Completed in dowloading {anime_title}-{episodes_range}; Successful:{len(successful_downloads)}, Failed:{len(failed_downloads)}" ) on_complete(successful_downloads, failed_downloads, anime_title) @@ -443,9 +440,10 @@ class AnimdlApi: ) file_name = episode + ".mp4" download_location = os.path.join(output_path, file_name) - on_progress_ = lambda current_bytes, total_bytes: on_progress( - current_bytes, total_bytes, episode_info - ) + + def on_progress_(current_bytes, total_bytes): + return on_progress(current_bytes, total_bytes, episode_info) + isfailure = cls.download_with_mpv(video_url, download_location, on_progress_) if isfailure: raise Exception @@ -474,9 +472,9 @@ class AnimdlApi: Exception: incase anything goes wrong """ - on_progress_ = lambda current_bytes, total_bytes: on_progress( - current_bytes, total_bytes, episode_info - ) + def on_progress_(current_bytes, total_bytes): + return on_progress(current_bytes, total_bytes, episode_info) + episode = ( path_parser(episode_info["anime_title"]) + " - " @@ -484,13 +482,11 @@ class AnimdlApi: ) sub_filename = episode + ".ass" sub_filepath = os.path.join(output_path, sub_filename) - is_sub_failure = cls.download_with_mpv(sub_url, sub_filepath, on_progress_) + cls.download_with_mpv(sub_url, sub_filepath, on_progress_) audio_filename = episode + ".mp3" audio_filepath = os.path.join(output_path, audio_filename) - is_audio_failure = cls.download_with_mpv( - audio_url, audio_filepath, on_progress_ - ) + cls.download_with_mpv(audio_url, audio_filepath, on_progress_) video_filename = episode + ".mp4" video_filepath = os.path.join(output_path, video_filename) @@ -523,9 +519,9 @@ class AnimdlApi: Exception: when anything goes wrong """ - on_progress_ = lambda current_bytes, total_bytes: on_progress( - current_bytes, total_bytes, episode_info - ) + def on_progress_(current_bytes, total_bytes): + return on_progress(current_bytes, total_bytes, episode_info) + episode = ( path_parser(episode_info["anime_title"]) + " - " @@ -533,7 +529,7 @@ class AnimdlApi: ) sub_filename = episode + ".ass" sub_filepath = os.path.join(output_path, sub_filename) - is_sub_failure = cls.download_with_mpv(sub_url, sub_filepath, on_progress_) + cls.download_with_mpv(sub_url, sub_filepath, on_progress_) video_filename = episode + ".mp4" video_filepath = os.path.join(output_path, video_filename) diff --git a/anixstream/libs/animdl/animdl_data_helper.py b/anixstream/libs/animdl/animdl_data_helper.py index 7621434..c1b3923 100644 --- a/anixstream/libs/animdl/animdl_data_helper.py +++ b/anixstream/libs/animdl/animdl_data_helper.py @@ -1,15 +1,14 @@ -import re import json +import re from fuzzywuzzy import fuzz -from .extras import Logger from .animdl_types import ( - AnimdlAnimeUrlAndTitle, AnimdlAnimeEpisode, + AnimdlAnimeUrlAndTitle, AnimdlEpisodeStream, ) - +from .extras import Logger # Currently this links don't work so we filter it out broken_link_pattern = r"https://tools.fast4speed.rsvp/\w*" @@ -65,7 +64,7 @@ def anime_title_percentage_match( """ percentage_ratio = fuzz.ratio(title, possible_user_requested_anime_title) - Logger.info( + Logger.debug( f"Animdl Api Fuzzy: Percentage match of {possible_user_requested_anime_title} against {title}: {percentage_ratio}%" ) return percentage_ratio @@ -83,9 +82,11 @@ def filter_broken_streams( list[AnimdlEpisodeStream]: the valid streams """ - stream_filter = lambda stream: ( - True if not re.match(broken_link_pattern, stream["stream_url"]) else False - ) + def stream_filter(stream): + return ( + True if not re.match(broken_link_pattern, stream["stream_url"]) else False + ) + return list(filter(stream_filter, streams)) @@ -104,9 +105,9 @@ def filter_streams_by_quality( """ # get the appropriate stream or default to best - get_quality_func = lambda stream_: ( - stream_.get("quality") if stream_.get("quality") else 0 - ) + def get_quality_func(stream_): + return stream_.get("quality") if stream_.get("quality") else 0 + match quality: case "best": return max(anime_episode_streams, key=get_quality_func) @@ -118,7 +119,7 @@ def filter_streams_by_quality( return episode_stream else: # if not strict: - Logger.info(f"Animdl Api: Not strict so defaulting to best") + Logger.debug("Animdl Api: Not strict so defaulting to best") return max(anime_episode_streams, key=get_quality_func) # else: # Logger.warning( @@ -195,6 +196,6 @@ def search_output_parser(raw_data: str) -> list[AnimdlAnimeUrlAndTitle]: parsed_data.append(AnimdlAnimeUrlAndTitle(anime_title, data[(i + 1)])) else: parsed_data.append(AnimdlAnimeUrlAndTitle(anime_title, item[1])) - except: + except Exception: pass return parsed_data # anime title,url diff --git a/anixstream/libs/animdl/animdl_exceptions.py b/anixstream/libs/animdl/animdl_exceptions.py index 2afe589..13e66de 100644 --- a/anixstream/libs/animdl/animdl_exceptions.py +++ b/anixstream/libs/animdl/animdl_exceptions.py @@ -15,4 +15,4 @@ class NoValidAnimeStreamsException(Exception): class InvalidAnimdlCommandsException(Exception): - pass \ No newline at end of file + pass diff --git a/anixstream/libs/animdl/animdl_types.py b/anixstream/libs/animdl/animdl_types.py index dfad603..28ef124 100644 --- a/anixstream/libs/animdl/animdl_types.py +++ b/anixstream/libs/animdl/animdl_types.py @@ -5,19 +5,20 @@ class AnimdlAnimeUrlAndTitle(NamedTuple): anime_title: str animdl_anime_url: str + class AnimdlEpisodeStream(TypedDict): - stream_url:str - quality:int - subtitle:list[str] | None + stream_url: str + quality: int + subtitle: list[str] | None audio_tracks: list[str] | None - title:str|None + title: str | None class AnimdlAnimeEpisode(TypedDict): - episode:int - streams:list[AnimdlEpisodeStream] - -class AnimdlData(NamedTuple): - anime_title:str - episodes:list[AnimdlAnimeEpisode] + episode: int + streams: list[AnimdlEpisodeStream] + +class AnimdlData(NamedTuple): + anime_title: str + episodes: list[AnimdlAnimeEpisode] diff --git a/anixstream/libs/animdl/extras.py b/anixstream/libs/animdl/extras.py index f218cc9..e6fd625 100644 --- a/anixstream/libs/animdl/extras.py +++ b/anixstream/libs/animdl/extras.py @@ -1,7 +1,7 @@ import logging Logger = logging.getLogger(__name__) -Logger.setLevel(logging.DEBUG) +# Logger.setLevel(logging.DEBUG) # formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") # console_handler = logging.StreamHandler() # console_handler.setLevel(logging.INFO) diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000..2d49b3a --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,4 @@ +{ + "venv": ".venv", + "venvPath": "." +}