diff --git a/.gitignore b/.gitignore index 5d3ca03..ee6041a 100644 --- a/.gitignore +++ b/.gitignore @@ -166,4 +166,10 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ +app/anixstream.ini +app/settings.json +app/user_data.json +app/View/SearchScreen/.search_screen.py.un~ +app/View/SearchScreen/search_screen.py~ +app/user_data.json diff --git a/app/Controller/__init__.py b/app/Controller/__init__.py index af568e3..e016c78 100644 --- a/app/Controller/__init__.py +++ b/app/Controller/__init__.py @@ -1,4 +1,7 @@ from .home_screen import HomeScreenController from .search_screen import SearchScreenController from .my_list_screen import MyListScreenController -from .anime_screen import AnimeScreenController \ No newline at end of file +from .anime_screen import AnimeScreenController +from .downloads_screen import DownloadsScreenController +from .help_screen import HelpScreenController +from .crashlog_screen import CrashLogScreenController \ No newline at end of file diff --git a/app/Controller/crashlog_screen.py b/app/Controller/crashlog_screen.py new file mode 100644 index 0000000..a392447 --- /dev/null +++ b/app/Controller/crashlog_screen.py @@ -0,0 +1,15 @@ + +from inspect import isgenerator +from View import CrashLogScreenView +from Model import CrashLogScreenModel +from View.components import MediaCardsContainer +from Utility import show_notification +from kivy.clock import Clock +class CrashLogScreenController: + def __init__(self, model:CrashLogScreenModel): + self.model = model + self.view = CrashLogScreenView(controller=self, model=self.model) + # self.update_anime_view() + + def get_view(self) -> CrashLogScreenView: + return self.view diff --git a/app/Controller/downloads_screen.py b/app/Controller/downloads_screen.py new file mode 100644 index 0000000..b5fac56 --- /dev/null +++ b/app/Controller/downloads_screen.py @@ -0,0 +1,15 @@ + +from inspect import isgenerator +from View import DownloadsScreenView +from Model import DownloadsScreenModel +from View.components import MediaCardsContainer +from Utility import show_notification +from kivy.clock import Clock +class DownloadsScreenController: + def __init__(self, model:DownloadsScreenModel): + self.model = model + self.view = DownloadsScreenView(controller=self, model=self.model) + # self.update_anime_view() + + def get_view(self) -> DownloadsScreenView: + return self.view diff --git a/app/Controller/help_screen.py b/app/Controller/help_screen.py new file mode 100644 index 0000000..ae0841b --- /dev/null +++ b/app/Controller/help_screen.py @@ -0,0 +1,15 @@ + +from inspect import isgenerator +from View import HelpScreenView +from Model import HelpScreenModel +from View.components import MediaCardsContainer +from Utility import show_notification +from kivy.clock import Clock +class HelpScreenController: + def __init__(self, model:HelpScreenModel): + self.model = model + self.view = HelpScreenView(controller=self, model=self.model) + # self.update_anime_view() + + def get_view(self) -> HelpScreenView: + return self.view diff --git a/app/Controller/home_screen.py b/app/Controller/home_screen.py index f77d7ef..58d08c1 100644 --- a/app/Controller/home_screen.py +++ b/app/Controller/home_screen.py @@ -18,7 +18,8 @@ class HomeScreenController: def __init__(self, model:HomeScreenModel): self.model = model # Model.main_screen.MainScreenModel self.view = HomeScreenView(controller=self, model=self.model) - Clock.schedule_once(lambda _:self.populate_home_screen()) + if self.view.app.config.get("Preferences","is_startup_anime_enable")=="1": + Clock.schedule_once(lambda _:self.populate_home_screen()) def get_view(self) -> HomeScreenView: return self.view diff --git a/app/Model/__init__.py b/app/Model/__init__.py index c1103f1..8f17a6f 100644 --- a/app/Model/__init__.py +++ b/app/Model/__init__.py @@ -1,4 +1,7 @@ from .home_screen import HomeScreenModel from .search_screen import SearchScreenModel from .my_list_screen import MyListScreenModel -from .anime_screen import AnimeScreenModel \ No newline at end of file +from .anime_screen import AnimeScreenModel +from .crashlog_screen import CrashLogScreenModel +from .help_screen import HelpScreenModel +from .download_screen import DownloadsScreenModel \ No newline at end of file diff --git a/app/Model/crashlog_screen.py b/app/Model/crashlog_screen.py new file mode 100644 index 0000000..98e4aae --- /dev/null +++ b/app/Model/crashlog_screen.py @@ -0,0 +1,7 @@ +from Model.base_model import BaseScreenModel + + +class CrashLogScreenModel(BaseScreenModel): + """ + Handles the crashlog screen logic + """ \ No newline at end of file diff --git a/app/Model/download_screen.py b/app/Model/download_screen.py new file mode 100644 index 0000000..5b41d86 --- /dev/null +++ b/app/Model/download_screen.py @@ -0,0 +1,7 @@ +from Model.base_model import BaseScreenModel + + +class DownloadsScreenModel(BaseScreenModel): + """ + Handles the download screen logic + """ \ No newline at end of file diff --git a/app/Model/help_screen.py b/app/Model/help_screen.py new file mode 100644 index 0000000..e060579 --- /dev/null +++ b/app/Model/help_screen.py @@ -0,0 +1,7 @@ +from Model.base_model import BaseScreenModel + + +class HelpScreenModel(BaseScreenModel): + """ + Handles the help screen logic + """ \ No newline at end of file diff --git a/app/View/AnimeScreen/__init__.py b/app/View/AnimeScreen/__init__.py index e69de29..9ed563a 100644 --- a/app/View/AnimeScreen/__init__.py +++ b/app/View/AnimeScreen/__init__.py @@ -0,0 +1 @@ +from .components.animdl_stream_dialog import AnimdlStreamDialog \ No newline at end of file diff --git a/app/View/AnimeScreen/anime_screen.kv b/app/View/AnimeScreen/anime_screen.kv index 568b0d0..349c711 100644 --- a/app/View/AnimeScreen/anime_screen.kv +++ b/app/View/AnimeScreen/anime_screen.kv @@ -34,6 +34,7 @@ height: self.minimum_height AnimeSideBar: id:side_bar + screen:root size_hint_y:None height:max(self.parent.height,self.minimum_height) @@ -45,6 +46,7 @@ RankingsBar: id:rankings_bar Controls: + screen:root MDBoxLayout: adaptive_height:True padding:"20dp" diff --git a/app/View/AnimeScreen/anime_screen.py b/app/View/AnimeScreen/anime_screen.py index d3482d8..907ead8 100644 --- a/app/View/AnimeScreen/anime_screen.py +++ b/app/View/AnimeScreen/anime_screen.py @@ -6,8 +6,9 @@ from kivymd.uix.label import MDLabel from kivy.utils import QueryDict,get_hex_from_color from collections import defaultdict +from . import AnimdlStreamDialog - +# TODO:move the rest of the classes to their own files class RankingsBar(MDBoxLayout): rankings = DictProperty( @@ -22,6 +23,7 @@ class RankingsBar(MDBoxLayout): class AnimeDescription(MDBoxLayout): description = StringProperty() + class AnimeCharacter(MDBoxLayout): voice_actors = ObjectProperty({ "name":"", @@ -36,6 +38,7 @@ class AnimeCharacter(MDBoxLayout): "description":"" }) + class AnimeCharacters(MDBoxLayout): container = ObjectProperty() characters = ListProperty() @@ -62,6 +65,7 @@ class AnimeCharacters(MDBoxLayout): # anime_character.voice_actor = self.container.add_widget(anime_character) + class AnimeReview(MDBoxLayout): review = ObjectProperty({ "username":"", @@ -69,6 +73,7 @@ class AnimeReview(MDBoxLayout): "summary":"" }) + class AnimeReviews(MDBoxLayout): reviews = ListProperty() container = ObjectProperty() @@ -83,16 +88,22 @@ class AnimeReviews(MDBoxLayout): } self.container.add_widget(review_) + class AnimeHeader(MDBoxLayout): titles = StringProperty() banner_image = StringProperty() + class SideBarLabel(MDLabel): pass + + class SideBarHeaderLabel(MDLabel): pass + class AnimeSideBar(MDBoxLayout): + screen = ObjectProperty() image = StringProperty() alternative_titles = DictProperty({ "synonyms":"", @@ -163,6 +174,10 @@ class AnimeSideBar(MDBoxLayout): site[1]) self.external_links_container.add_widget(label) +class Controls(MDBoxLayout): + screen = ObjectProperty() + + class AnimeScreenView(BaseScreenView): header:AnimeHeader = ObjectProperty() side_bar:AnimeSideBar = ObjectProperty() @@ -170,7 +185,7 @@ class AnimeScreenView(BaseScreenView): anime_description:AnimeDescription = ObjectProperty() anime_characters:AnimeCharacters = ObjectProperty() anime_reviews:AnimeReviews = ObjectProperty() - + data = DictProperty() def model_is_changed(self) -> None: """ Called whenever any change has occurred in the data model. @@ -179,6 +194,7 @@ class AnimeScreenView(BaseScreenView): """ def update_layout(self,data): + self.data = data # uitlity functions format_date = lambda date_: f"{date_['day']}/{date_['month']}/{date_['year']}" if date_ else "" format_list_with_comma = lambda list_: ", ".join(list_) if list_ else "" @@ -264,4 +280,10 @@ class AnimeScreenView(BaseScreenView): self.anime_reviews.reviews = data["reviews"]["nodes"] # for r in data["recommendation"]["nodes"]: - # r["mediaRecommendation"] \ No newline at end of file + # r["mediaRecommendation"] + + def stream_anime_with_custom_cmds_dialog(self): + """ + Called when user wants to stream with custom commands + """ + AnimdlStreamDialog(self.data).open() \ No newline at end of file diff --git a/app/View/AnimeScreen/components/animdl_download_dialog.kv b/app/View/AnimeScreen/components/animdl_download_dialog.kv new file mode 100644 index 0000000..e69de29 diff --git a/app/View/AnimeScreen/components/animdl_stream_dialog.kv b/app/View/AnimeScreen/components/animdl_stream_dialog.kv new file mode 100644 index 0000000..3c745c8 --- /dev/null +++ b/app/View/AnimeScreen/components/animdl_stream_dialog.kv @@ -0,0 +1,63 @@ +: + adaptive_height:True + max_lines:0 + shorten:False + markup:True + font_style: "Label" + role: "medium" + bold:True +: + adaptive_height:True + halign:"center" + max_lines:0 + shorten:False + bold:True + markup:True + font_style: "Title" + role: "medium" + md_bg_color:self.theme_cls.secondaryContainerColor + padding:"10dp" + + + + + md_bg_color:self.theme_cls.backgroundColor + radius:8 + size_hint:None,None + height:"500dp" + width:"400dp" + MDBoxLayout: + spacing: '10dp' + padding:"10dp" + orientation:"vertical" + StreamDialogHeaderLabel: + text:"Stream on Animdl" + StreamDialogLabel: + text:"Title" + MDTextField: + id:title_field + required:True + StreamDialogLabel: + text:"Range" + MDTextField: + id:range_field + required:True + StreamDialogLabel: + text:"Latest" + MDTextField: + id:latest_field + required:True + StreamDialogLabel: + text:"Quality" + MDTextField: + id:quality_field + required:True + MDBoxLayout: + orientation:"vertical" + MDButton: + pos_hint: {'center_x': 0.5} + on_press:root.stream_anime(app) + MDButtonIcon: + icon:"rss" + MDButtonText: + text:"Stream" \ No newline at end of file diff --git a/app/View/AnimeScreen/components/animdl_stream_dialog.py b/app/View/AnimeScreen/components/animdl_stream_dialog.py new file mode 100644 index 0000000..be42c7e --- /dev/null +++ b/app/View/AnimeScreen/components/animdl_stream_dialog.py @@ -0,0 +1,34 @@ +from kivy.uix.modalview import ModalView +from kivymd.uix.behaviors import StencilBehavior,CommonElevationBehavior,BackgroundColorBehavior +from kivymd.theming import ThemableBehavior +# from main import AniXStreamApp +class AnimdlStreamDialog(ThemableBehavior,StencilBehavior,CommonElevationBehavior,BackgroundColorBehavior,ModalView): + def __init__(self,data,**kwargs): + super(AnimdlStreamDialog,self).__init__(**kwargs) + self.data = data + if title:=data["title"].get("romaji"): + self.ids.title_field.text = title + elif title:=data["title"].get("english"): + self.ids.title_field.text = title + + self.ids.quality_field.text = "best" + def stream_anime(self,app): + cmds = [] + title = self.ids.title_field.text + cmds.append(title) + + episodes_range = self.ids.range_field.text + if episodes_range: + cmds = [*cmds,"-r",episodes_range] + + latest = self.ids.latest_field.text + if latest: + cmds = [*cmds,"-s",latest] + + quality = self.ids.quality_field.text + if quality: + cmds = [*cmds,"-q",quality] + + # print(title,episodes_range,latest,quality) + print(cmds) + app.watch_on_animdl(custom_options = cmds) \ No newline at end of file diff --git a/app/View/AnimeScreen/components/controls.kv b/app/View/AnimeScreen/components/controls.kv index 3715843..37d56c4 100644 --- a/app/View/AnimeScreen/components/controls.kv +++ b/app/View/AnimeScreen/components/controls.kv @@ -1,4 +1,4 @@ - + adaptive_height:True padding:"10dp" spacing:"10dp" @@ -8,10 +8,11 @@ MDButtonText: text:"Add to MyList" MDButton: - on_press: print("presed") + on_press: + if root.screen:root.screen.stream_anime_with_custom_cmds_dialog() MDButtonText: text:"Watch on Animdl" MDButton: - on_press: print("presed") + on_press: app.watch_on_allanime(root.screen.data["title"]["romaji"]) MDButtonText: text:"Watch on AllAnime" diff --git a/app/View/AnimeScreen/components/side_bar.kv b/app/View/AnimeScreen/components/side_bar.kv index 43a7c13..02af27a 100644 --- a/app/View/AnimeScreen/components/side_bar.kv +++ b/app/View/AnimeScreen/components/side_bar.kv @@ -46,7 +46,8 @@ width:dp(200) pos_hint: {'center_x': 0.5} MDButton: - on_press:app.watch_on_animdl(root.alternative_titles) + on_press: + root.screen.stream_anime_with_custom_cmds_dialog() pos_hint: {'center_x': 0.5} MDButtonText: diff --git a/app/View/CrashLogScreen/crashlog_screen.kv b/app/View/CrashLogScreen/crashlog_screen.kv new file mode 100644 index 0000000..2944391 --- /dev/null +++ b/app/View/CrashLogScreen/crashlog_screen.kv @@ -0,0 +1,18 @@ +#:import get_color_from_hex kivy.utils.get_color_from_hex +#:import StringProperty kivy.properties.StringProperty + + + md_bg_color: self.theme_cls.backgroundColor + # main_container:main_container + MDBoxLayout: + NavRail: + screen:root + MDAnchorLayout: + anchor_y: 'top' + padding:"10dp" + MDBoxLayout: + orientation: 'vertical' + SearchBar: + + MDLabel: + text:"Crash Log" \ No newline at end of file diff --git a/app/View/CrashLogScreen/crashlog_screen.py b/app/View/CrashLogScreen/crashlog_screen.py new file mode 100644 index 0000000..4903e61 --- /dev/null +++ b/app/View/CrashLogScreen/crashlog_screen.py @@ -0,0 +1,12 @@ +from kivy.properties import ObjectProperty +from View.base_screen import BaseScreenView + + +class CrashLogScreenView(BaseScreenView): + main_container = ObjectProperty() + def model_is_changed(self) -> None: + """ + Called whenever any change has occurred in the data model. + The view in this method tracks these changes and updates the UI + according to these changes. + """ diff --git a/app/View/DownloadsScreen/components/status_bar.kv b/app/View/DownloadsScreen/components/status_bar.kv new file mode 100644 index 0000000..e69de29 diff --git a/app/View/DownloadsScreen/download_screen.kv b/app/View/DownloadsScreen/download_screen.kv new file mode 100644 index 0000000..8906643 --- /dev/null +++ b/app/View/DownloadsScreen/download_screen.kv @@ -0,0 +1,18 @@ +#:import get_color_from_hex kivy.utils.get_color_from_hex +#:import StringProperty kivy.properties.StringProperty + + + md_bg_color: self.theme_cls.backgroundColor + # main_container:main_container + MDBoxLayout: + NavRail: + screen:root + MDAnchorLayout: + anchor_y: 'top' + padding:"10dp" + MDBoxLayout: + orientation: 'vertical' + SearchBar: + + MDLabel: + text:"Downloads" \ No newline at end of file diff --git a/app/View/DownloadsScreen/download_screen.py b/app/View/DownloadsScreen/download_screen.py new file mode 100644 index 0000000..1d9c98b --- /dev/null +++ b/app/View/DownloadsScreen/download_screen.py @@ -0,0 +1,16 @@ +from kivy.properties import ObjectProperty +from View.base_screen import BaseScreenView +from kivy.uix.modalview import ModalView + +class DownloadAnimePopup(ModalView): + pass + +class DownloadsScreenView(BaseScreenView): + main_container = ObjectProperty() + def model_is_changed(self) -> None: + """ + Called whenever any change has occurred in the data model. + The view in this method tracks these changes and updates the UI + according to these changes. + """ + diff --git a/app/View/HelpScreen/help_screen.kv b/app/View/HelpScreen/help_screen.kv new file mode 100644 index 0000000..740c520 --- /dev/null +++ b/app/View/HelpScreen/help_screen.kv @@ -0,0 +1,18 @@ +#:import get_color_from_hex kivy.utils.get_color_from_hex +#:import StringProperty kivy.properties.StringProperty + + + md_bg_color: self.theme_cls.backgroundColor + # main_container:main_container + MDBoxLayout: + NavRail: + screen:root + MDAnchorLayout: + anchor_y: 'top' + padding:"10dp" + MDBoxLayout: + orientation: 'vertical' + SearchBar: + + MDLabel: + text:"Help Screen" \ No newline at end of file diff --git a/app/View/HelpScreen/help_screen.py b/app/View/HelpScreen/help_screen.py new file mode 100644 index 0000000..4155b6d --- /dev/null +++ b/app/View/HelpScreen/help_screen.py @@ -0,0 +1,13 @@ +from kivy.properties import ObjectProperty +from View.base_screen import BaseScreenView + + +class HelpScreenView(BaseScreenView): + main_container = ObjectProperty() + def model_is_changed(self) -> None: + """ + Called whenever any change has occurred in the data model. + The view in this method tracks these changes and updates the UI + according to these changes. + """ + \ No newline at end of file diff --git a/app/View/__init__.py b/app/View/__init__.py index e9c3920..fd9ba30 100644 --- a/app/View/__init__.py +++ b/app/View/__init__.py @@ -1,4 +1,12 @@ +# 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 \ No newline at end of file +from .AnimeScreen.anime_screen import AnimeScreenView +from .CrashLogScreen.crashlog_screen import CrashLogScreenView +from .DownloadsScreen.download_screen import DownloadsScreenView +from .HelpScreen.help_screen import HelpScreenView + +# others +from .components.animdl_dialog.animdl_dialog import AnimdlDialogPopup +from .DownloadsScreen.download_screen import DownloadAnimePopup \ No newline at end of file diff --git a/app/View/components/animdl_dialog/animdl_dialog.kv b/app/View/components/animdl_dialog/animdl_dialog.kv new file mode 100644 index 0000000..e69de29 diff --git a/app/View/components/animdl_dialog/animdl_dialog.py b/app/View/components/animdl_dialog/animdl_dialog.py new file mode 100644 index 0000000..f7ce40c --- /dev/null +++ b/app/View/components/animdl_dialog/animdl_dialog.py @@ -0,0 +1,4 @@ +from kivy.uix.modalview import ModalView + +class AnimdlDialogPopup(ModalView): + pass \ No newline at end of file diff --git a/app/View/components/general.kv b/app/View/components/general.kv index 31019bc..444dc32 100644 --- a/app/View/components/general.kv +++ b/app/View/components/general.kv @@ -1,3 +1,3 @@ -: - allow_copy:True - allow_selection:True \ No newline at end of file +# : +# allow_copy:True +# allow_selection:True \ No newline at end of file diff --git a/app/View/components/navrail.kv b/app/View/components/navrail.kv index 159fe62..22ac36f 100644 --- a/app/View/components/navrail.kv +++ b/app/View/components/navrail.kv @@ -30,14 +30,21 @@ on_press: root.screen.manager_screens.current = "my list screen" CommonNavigationRailItem: - icon: "library" - text: "Library" + icon: "download-circle" + text: "Downloads" on_press: - root.screen.manager_screens.current = "anime screen" + root.screen.manager_screens.current = "downloads screen" CommonNavigationRailItem: icon: "cog" text: "settings" on_press:app.open_settings() + CommonNavigationRailItem: + icon: "help-circle" + text: "Help" + on_press: + root.screen.manager_screens.current = "help screen" CommonNavigationRailItem: icon: "bug" text: "debug" + on_press: + root.screen.manager_screens.current = "crashlog screen" diff --git a/app/View/screens.py b/app/View/screens.py index 49cd9b7..8dd3099 100644 --- a/app/View/screens.py +++ b/app/View/screens.py @@ -1,5 +1,5 @@ -from Controller import SearchScreenController,HomeScreenController,MyListScreenController,AnimeScreenController -from Model import HomeScreenModel,SearchScreenModel,MyListScreenModel,AnimeScreenModel +from Controller import (SearchScreenController,HomeScreenController,MyListScreenController,AnimeScreenController,DownloadsScreenController,HelpScreenController,CrashLogScreenController) +from Model import (HomeScreenModel,SearchScreenModel,MyListScreenModel,AnimeScreenModel,DownloadsScreenModel,HelpScreenModel,CrashLogScreenModel) screens = { @@ -19,4 +19,16 @@ screens = { "model": AnimeScreenModel, "controller": AnimeScreenController, }, + "crashlog screen": { + "model": CrashLogScreenModel, + "controller": CrashLogScreenController, + }, + "downloads screen": { + "model": DownloadsScreenModel, + "controller": DownloadsScreenController, + }, + "help screen": { + "model": HelpScreenModel, + "controller": HelpScreenController, + }, } \ No newline at end of file diff --git a/app/libs/animdl/.python-version b/app/libs/animdl/.python-version deleted file mode 100644 index e4fba21..0000000 --- a/app/libs/animdl/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.12 diff --git a/app/libs/animdl/.qu.graphql.un~ b/app/libs/animdl/.qu.graphql.un~ deleted file mode 100644 index a7df890..0000000 Binary files a/app/libs/animdl/.qu.graphql.un~ and /dev/null differ diff --git a/app/libs/animdl/animdl_api.py b/app/libs/animdl/animdl_api.py index cddb607..ae3c06d 100644 --- a/app/libs/animdl/animdl_api.py +++ b/app/libs/animdl/animdl_api.py @@ -19,6 +19,21 @@ class AnimdlApi: else: return run([py_path,"-m", "animdl", *cmds]) + @classmethod + def run_custom_command(cls,*cmds:tuple[str])->Popen: + """ + Runs an AnimDl custom command with the full power of animdl and returns a subprocess(popen) for full control + """ + + # TODO: parse the commands + parsed_cmds = list(cmds) + + if py_path:=shutil.which("python"): + base_cmds = [py_path,"-m","animdl"] + child_process = Popen([*base_cmds,*parsed_cmds]) + return child_process + + @classmethod def stream_anime_by_title(cls,title,episodes_range=None): anime = cls.get_anime_url_by_title(title) @@ -183,7 +198,7 @@ class AnimdlApi: possible_animes = cls.output_parser(result) if possible_animes: anime = max(possible_animes.items(),key=lambda anime_item:cls.get_anime_match(anime_item,title)) - return anime # {"title","anime url"} + return anime # ("title","anime url") return None @classmethod diff --git a/app/main.py b/app/main.py index 4beb095..a57ea23 100644 --- a/app/main.py +++ b/app/main.py @@ -13,6 +13,11 @@ import json from queue import Queue from threading import Thread import plyer +# plyer.facades.StoragePath.get_application_dir +# plyer.facades.StoragePath.get_downloads_dir +# plyer.facades.StoragePath.get_videos_dir() +# plyer.facades.StoragePath. +# plyer.facades.StoragePath.get_application_dir from kivymd.app import MDApp from kivy.uix.settings import SettingsWithSidebar,Settings @@ -22,9 +27,11 @@ from kivy.storage.jsonstore import JsonStore from datetime import date,datetime from subprocess import Popen from View.screens import screens - +from View import DownloadAnimePopup,AnimdlDialogPopup import time -from Utility import themes_available + +import webbrowser +from Utility import themes_available,show_notification user_data = JsonStore("user_data.json") today = date.today() @@ -43,13 +50,18 @@ elif not( yt_cache.get("yt_stream_links").get(f"{links_cache_name}")): class AniXStreamApp(MDApp): queue = Queue() + downloads_queue = Queue() animdl_streaming_subprocess:Popen|None = None def worker(self,queue:Queue): while True: task = queue.get() # task should be a function task() self.queue.task_done() - + def downloads_worker(self,queue:Queue): + while True: + download_task = queue.get() # task should be a function + download_task() + self.queue.task_done() def __init__(self, **kwargs): super().__init__(**kwargs) @@ -64,6 +76,11 @@ class AniXStreamApp(MDApp): self.worker_thread.daemon = True self.worker_thread.start() + # initialize downloads worker + self.downloads_worker_thread = Thread(target=self.downloads_worker,args=(self.downloads_queue,)) + self.downloads_worker_thread.daemon = True + self.downloads_worker_thread.start() + def build(self) -> ScreenManager: self.settings_cls = SettingsWithSidebar self.generate_application_screens() @@ -92,8 +109,10 @@ class AniXStreamApp(MDApp): config.setdefaults('Preferences', { 'theme_color': 'Cyan', "theme_style": "Dark", - "downloads_dir":"." + "downloads_dir": plyer.storagepath.get_videos_dir() if plyer.storagepath.get_videos_dir() else ".", + "is_startup_anime_enable":False }) + print(self.config.get("Preferences","is_startup_anime_enable")) def build_settings(self,settings:Settings): settings.add_json_panel("Settings",self.config,"settings.json") @@ -110,7 +129,14 @@ class AniXStreamApp(MDApp): config.write() case "theme_style": self.theme_cls.theme_style = value + + def on_stop(self): + if self.animdl_streaming_subprocess: + self.animdl_streaming_subprocess.terminate() + + # custom methods + # TODO: move theme to a personalized class def search_for_anime(self,search_field,**kwargs): if self.manager_screens.current != "search screen": self.manager_screens.current = "search screen" @@ -120,21 +146,75 @@ class AniXStreamApp(MDApp): self.manager_screens.current = "anime screen" self.anime_screen.controller.update_anime_view(id) - def stream_anime_with_animdl(self,title): - self.animdl_streaming_subprocess = AnimdlApi.stream_anime_by_title(title) - self.stop_streaming = False + def watch_on_allanime(self,title_): + """ + Opens the given anime in your default browser on allanimes site + Parameters: + ---------- + title_: The anime title requested to be opened + """ + if anime:=AnimdlApi.get_anime_url_by_title(title_): + title,link = anime + parsed_link = f"https://allmanga.to/bangumi/{link.split('/')[-1]}" + else: + show_notification("Failure",f"Failed to open {title} in browser on allanime site") + if webbrowser.open(parsed_link): + show_notification("Success",f"Successfully opened {title} in browser allanime site") + else: + show_notification("Failure",f"Failed to open {title} in browser on allanime site") + + + + def open_download_anime_dialog(self): + """" + calls the download dialog + """ + DownloadAnimePopup().open() + def download_anime(self,on_complete,on_progress,default_cmds:dict[str,str]|None=None,custom_cmds:tuple[str]|None=None): + # TODO:Add custom download cmds functionality + output_path = self.config.get("Preferences","downloads_dir") + if default_cmds: + if episodes_range:=default_cmds.get("episodes_range"): + download_task =lambda: AnimdlApi.download_anime_by_title(default_cmds["title"],on_progress,on_complete,output_path,episodes_range,default_cmds["quality"]) + self.downloads_queue.put(download_task) + else: + download_task =lambda: AnimdlApi.download_anime_by_title(default_cmds["title"],on_progress,on_complete,output_path,None,default_cmds["quality"]) + self.downloads_queue.put(download_task) + + def stream_anime_with_custom_input_cmds(self,*cmds): + self.animdl_streaming_subprocess = AnimdlApi.run_custom_command("stream",*cmds) + + def stream_anime_by_title_with_animdl(self,title,episodes_range:str|None=None): + self.animdl_streaming_subprocess = AnimdlApi.stream_anime_by_title(title,episodes_range) + # self.stop_streaming = False - def watch_on_animdl(self,title_dict:dict): + def watch_on_animdl(self,title_dict:dict|None=None,episodes_range:str|None=None,custom_options:tuple[str]|None=None): + """ + Enables you to stream an anime using animdl either by parsing a title or custom animdl options + + parameters: + ----------- + title_dict:dict["japanese","kanji"] + a dictionary containing the titles of the anime + custom_options:tuple[str] + a tuple containing valid animdl stream commands + """ if self.animdl_streaming_subprocess: self.animdl_streaming_subprocess.terminate() - if title:=title_dict.get("japanese"): - stream_func = lambda: self.stream_anime_with_animdl(title) - self.queue.put(stream_func) - elif title:=title_dict.get("english"): - stream_func = lambda:self.stream_anime_with_animdl(title) + + if title_dict: + if title:=title_dict.get("japanese"): + stream_func = lambda: self.stream_anime_by_title_with_animdl(title,episodes_range) + self.queue.put(stream_func) + elif title:=title_dict.get("english"): + stream_func = lambda:self.stream_anime_by_title_with_animdl(title,episodes_range) + self.queue.put(stream_func) + else: + stream_func = lambda:self.stream_anime_with_custom_input_cmds(*custom_options) self.queue.put(stream_func) if __name__ == "__main__": # try: AniXStreamApp().run() # except: + # print(plyer.storagepath.get_videos_dir()) \ No newline at end of file diff --git a/app/user_data.json b/app/user_data.json index 3c7c315..4273765 100644 --- a/app/user_data.json +++ b/app/user_data.json @@ -1 +1 @@ -{"my_list": {"list": [1535, 20605, 21519, 21, 5114, 133007, 166710, 116674, 11061, 21745, 9253, 153406, 166613, 133845, 143103, 20996, 162804]}} \ No newline at end of file +{"my_list": {"list": [1535, 20605, 21519, 21, 5114, 133007, 166710, 116674, 11061, 21745, 9253, 153406, 166613, 133845, 143103, 20996, 162804, 125367, 21827]}} \ No newline at end of file