mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-05 20:40:09 -08:00
added logging, completed anime liast screen and refined code base
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
|
||||
from inspect import isgenerator
|
||||
from View import AnimeScreenView
|
||||
from Model import AnimeScreenModel
|
||||
from View.components import MediaCardsContainer
|
||||
from Utility import show_notification
|
||||
from kivy.clock import Clock
|
||||
from kivy.logger import Logger
|
||||
from kivy.cache import Cache
|
||||
|
||||
from Model import AnimeScreenModel
|
||||
from View import AnimeScreenView
|
||||
|
||||
Cache.register("data.anime",limit=20,timeout=600)
|
||||
|
||||
class AnimeScreenController:
|
||||
def __init__(self, model:AnimeScreenModel):
|
||||
self.model = model
|
||||
@@ -13,12 +15,19 @@ class AnimeScreenController:
|
||||
def get_view(self) -> AnimeScreenView:
|
||||
return self.view
|
||||
|
||||
def update_anime_view(self,id:int):
|
||||
def update_anime_view(self,id:int,caller_screen_name:str):
|
||||
if self.model.anime_id != id:
|
||||
data = self.model.get_anime_data(id)
|
||||
if cached_anime_data:=Cache.get("data.anime",f"{id}"):
|
||||
data = cached_anime_data
|
||||
else:
|
||||
data = self.model.get_anime_data(id)
|
||||
|
||||
if data[0]:
|
||||
d = data[1]["data"]["Page"]["media"][0]
|
||||
self.model.anime_id = id
|
||||
Clock.schedule_once(lambda _:self.view.update_layout(data[1]["data"]["Page"]["media"][0]))
|
||||
Clock.schedule_once(lambda _:self.view.update_layout(d,caller_screen_name))
|
||||
Logger.info(f"Anime Screen:Success in opening anime of id: {id}")
|
||||
Cache.append("data.anime",f"{id}",data)
|
||||
|
||||
def update_my_list(self,*args):
|
||||
self.model.update_user_anime_list(*args)
|
||||
|
||||
@@ -13,3 +13,16 @@ class DownloadsScreenController:
|
||||
|
||||
def get_view(self) -> DownloadsScreenView:
|
||||
return self.view
|
||||
|
||||
# def requested_update_my_list_screen(self):
|
||||
# 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):
|
||||
# Logger.info("My List Screen:User anime list change;updating screen")
|
||||
# anime_cards = self.model.update_my_anime_list_view(animes_to_add)
|
||||
# self.model.already_in_user_anime_list = user_anime_list
|
||||
# if isgenerator(anime_cards):
|
||||
# for result_card in anime_cards:
|
||||
# result_card.screen = self.view
|
||||
# self.view.update_layout(result_card)
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
|
||||
from inspect import isgenerator
|
||||
from View.HomeScreen.home_screen import HomeScreenView
|
||||
from Model.home_screen import HomeScreenModel
|
||||
from View.components.media_card.media_card import MediaCardsContainer
|
||||
from Utility import show_notification
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.logger import Logger
|
||||
|
||||
from View import HomeScreenView
|
||||
from Model import HomeScreenModel
|
||||
from View.components import MediaCardsContainer
|
||||
from Utility import show_notification
|
||||
|
||||
|
||||
# TODO:Move the update home screen to homescreen.py
|
||||
class HomeScreenController:
|
||||
@@ -34,6 +38,7 @@ class HomeScreenController:
|
||||
most_popular_cards_container.container.add_widget(card)
|
||||
self.view.main_container.add_widget(most_popular_cards_container)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load most popular anime")
|
||||
self.populate_errors.append("Most Popular Anime")
|
||||
|
||||
def favourite_anime(self):
|
||||
@@ -46,6 +51,7 @@ class HomeScreenController:
|
||||
most_favourite_cards_container.container.add_widget(card)
|
||||
self.view.main_container.add_widget(most_favourite_cards_container)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load most favourite anime")
|
||||
self.populate_errors.append("Most favourite Anime")
|
||||
|
||||
def trending_anime(self):
|
||||
@@ -58,6 +64,7 @@ class HomeScreenController:
|
||||
trending_cards_container.container.add_widget(card)
|
||||
self.view.main_container.add_widget(trending_cards_container)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load trending anime")
|
||||
self.populate_errors.append("trending Anime")
|
||||
|
||||
def highest_scored_anime(self):
|
||||
@@ -70,10 +77,10 @@ class HomeScreenController:
|
||||
most_scored_cards_container.container.add_widget(card)
|
||||
self.view.main_container.add_widget(most_scored_cards_container)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load highest scored anime")
|
||||
self.populate_errors.append("Most scored Anime")
|
||||
|
||||
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()
|
||||
@@ -83,6 +90,7 @@ class HomeScreenController:
|
||||
most_recently_updated_cards_container.container.add_widget(card)
|
||||
self.view.main_container.add_widget(most_recently_updated_cards_container)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load recently updated anime")
|
||||
self.populate_errors.append("Most recently updated Anime")
|
||||
|
||||
def upcoming_anime(self):
|
||||
@@ -95,6 +103,7 @@ class HomeScreenController:
|
||||
upcoming_cards_container.container.add_widget(card)
|
||||
self.view.main_container.add_widget(upcoming_cards_container)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load upcoming anime")
|
||||
self.populate_errors.append("upcoming Anime")
|
||||
|
||||
def populate_home_screen(self):
|
||||
@@ -109,7 +118,6 @@ class HomeScreenController:
|
||||
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)}")
|
||||
|
||||
|
||||
def update_my_list(self,*args):
|
||||
self.model.update_user_anime_list(*args)
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
|
||||
from inspect import isgenerator
|
||||
|
||||
from kivy.logger import Logger
|
||||
from kivy.clock import Clock
|
||||
from kivy.utils import difference
|
||||
|
||||
from View import MyListScreenView
|
||||
from Model import MyListScreenModel
|
||||
from View.components.media_card.media_card import MediaCardsContainer
|
||||
from Utility import show_notification
|
||||
from Utility import show_notification,user_data_helper
|
||||
|
||||
class MyListScreenController:
|
||||
"""
|
||||
@@ -14,8 +17,21 @@ class MyListScreenController:
|
||||
"""
|
||||
|
||||
def __init__(self, model:MyListScreenModel):
|
||||
self.model = model # Model.main_screen.MyListScreenModel
|
||||
self.model = model
|
||||
self.view = MyListScreenView(controller=self, model=self.model)
|
||||
# self.populate_home_screen()
|
||||
self.requested_update_my_list_screen()
|
||||
def get_view(self) -> MyListScreenView:
|
||||
return self.view
|
||||
|
||||
def requested_update_my_list_screen(self):
|
||||
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):
|
||||
Logger.info("My List Screen:User anime list change;updating screen")
|
||||
anime_cards = self.model.update_my_anime_list_view(animes_to_add)
|
||||
self.model.already_in_user_anime_list = user_anime_list
|
||||
if isgenerator(anime_cards):
|
||||
for result_card in anime_cards:
|
||||
result_card.screen = self.view
|
||||
self.view.update_layout(result_card)
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
|
||||
from inspect import isgenerator
|
||||
from Utility import show_notification
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.logger import Logger
|
||||
|
||||
from View import SearchScreenView
|
||||
from Model import SearchScreenModel
|
||||
from kivy.clock import Clock
|
||||
from Utility import show_notification
|
||||
|
||||
class SearchScreenController:
|
||||
|
||||
def __init__(self, model:SearchScreenModel):
|
||||
self.model = model # Model.main_screen.MainScreenModel
|
||||
self.view = SearchScreenView(controller=self, model=self.model)
|
||||
|
||||
def get_view(self) -> SearchScreenView:
|
||||
return self.view
|
||||
|
||||
def update_trending_anime(self):
|
||||
trending_cards_generator = self.model.get_trending_anime()
|
||||
if isgenerator(trending_cards_generator):
|
||||
@@ -21,6 +25,7 @@ class SearchScreenController:
|
||||
card.pos_hint = {'center_x': 0.5}
|
||||
self.view.update_trending_sidebar(card)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load trending anime")
|
||||
self.populate_errors.append("trending Anime")
|
||||
|
||||
def requested_search_for_anime(self,anime_title,**kwargs):
|
||||
@@ -33,5 +38,6 @@ class SearchScreenController:
|
||||
Clock.schedule_once(lambda _:self.view.update_pagination(self.model.pagination_info))
|
||||
Clock.schedule_once(lambda _:self.update_trending_anime())
|
||||
else:
|
||||
Logger.error(f"Home Screen:Failed to search for {anime_title}")
|
||||
show_notification("Failed to search",f"{search_Results.get('Error')}")
|
||||
self.view.is_searching = False
|
||||
|
||||
@@ -5,3 +5,16 @@ class DownloadsScreenModel(BaseScreenModel):
|
||||
"""
|
||||
Handles the download screen logic
|
||||
"""
|
||||
|
||||
# already_in_user_anime_list = []
|
||||
# def update_my_anime_list_view(self,not_yet_in_user_anime_list:list,**kwargs):
|
||||
# success,self.data = AniList.search(id_in=not_yet_in_user_anime_list)
|
||||
# if success:
|
||||
# return self.media_card_generator()
|
||||
# else:
|
||||
# show_notification(f"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)
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
import os
|
||||
from Model.base_model import BaseScreenModel
|
||||
from Utility import show_notification
|
||||
from libs.anilist import AniList
|
||||
from Utility.media_card_loader import MediaCardLoader
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
from Model.base_model import BaseScreenModel
|
||||
from Utility import MediaCardLoader,show_notification
|
||||
|
||||
|
||||
user_data= JsonStore("user_data.json")
|
||||
class MyListScreenModel(BaseScreenModel):
|
||||
data = {}
|
||||
def search_for_anime(self,anime_title,**kwargs):
|
||||
success,self.data = AniList.search(query=anime_title,**kwargs)
|
||||
already_in_user_anime_list = []
|
||||
def update_my_anime_list_view(self,not_yet_in_user_anime_list:list,**kwargs):
|
||||
success,self.data = AniList.search(id_in=not_yet_in_user_anime_list)
|
||||
if success:
|
||||
return self.media_card_generator()
|
||||
else:
|
||||
show_notification(f"Failed to search for {anime_title}",self.data["Error"])
|
||||
show_notification(f"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)
|
||||
self.pagination_info = self.extract_pagination_info()
|
||||
|
||||
def extract_pagination_info(self):
|
||||
pagination_info = None
|
||||
return pagination_info
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import os
|
||||
from typing import Generator
|
||||
|
||||
from Model.base_model import BaseScreenModel
|
||||
from Utility import show_notification
|
||||
from libs.anilist import AniList
|
||||
from Utility.media_card_loader import MediaCardLoader
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
from View.components import MediaCard
|
||||
from Utility import show_notification
|
||||
|
||||
|
||||
user_data= JsonStore("user_data.json")
|
||||
class SearchScreenModel(BaseScreenModel):
|
||||
data = {}
|
||||
|
||||
def get_trending_anime(self):
|
||||
def get_trending_anime(self)->MediaCard|dict:
|
||||
success,data = AniList.get_trending()
|
||||
if success:
|
||||
def _data_generator():
|
||||
def _data_generator()->Generator[MediaCard,MediaCard,MediaCard]:
|
||||
for anime_item in data["data"]["Page"]["media"]:
|
||||
yield MediaCardLoader.media_card(anime_item)
|
||||
return _data_generator()
|
||||
|
||||
@@ -14,13 +14,13 @@ from Utility import anilist_data_helper, user_data_helper
|
||||
|
||||
|
||||
# Register anime cache in memory
|
||||
Cache.register("anime")
|
||||
Cache.register("yt_stream_links.anime")
|
||||
|
||||
user_anime_list = user_data_helper.get_user_animelist()
|
||||
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("anime",link[0],tuple(link[1]))
|
||||
Cache.append("yt_stream_links.anime",link[0],tuple(link[1]))
|
||||
|
||||
|
||||
# for youtube video links gotten from from pytube which is blocking
|
||||
@@ -72,7 +72,7 @@ class MediaCardDataLoader(object):
|
||||
self._resume_cond.release()
|
||||
|
||||
def cached_fetch_data(self,yt_watch_url):
|
||||
data:tuple = Cache.get("anime",yt_watch_url) # type: ignore # trailer_url is the yt_watch_link
|
||||
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)
|
||||
@@ -94,10 +94,9 @@ class MediaCardDataLoader(object):
|
||||
yt_watch_link = kwargs['yt_watch_link']
|
||||
try:
|
||||
data = self.cached_fetch_data(yt_watch_link)
|
||||
print("Update: ",data)
|
||||
except Exception as e:
|
||||
data = None
|
||||
print(f"Not accesible:{e} ")
|
||||
Logger.error("Pytube:{e}")
|
||||
|
||||
self._q_done.appendleft((yt_watch_link, data))
|
||||
self._trigger_update()
|
||||
@@ -118,20 +117,18 @@ class MediaCardDataLoader(object):
|
||||
yt_watch_link, data= self._q_done.pop()
|
||||
except IndexError:
|
||||
return
|
||||
# update client
|
||||
# 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:
|
||||
# client.set_preview_image(data[0])
|
||||
trailer_url = data[1]
|
||||
print(trailer_url,"-----------------")
|
||||
if trailer_url:
|
||||
client.set_trailer_url(trailer_url)
|
||||
print(client.title,self._running)
|
||||
Cache.append("anime",yt_watch_link,data)
|
||||
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()
|
||||
@@ -140,10 +137,10 @@ class MediaCardDataLoader(object):
|
||||
**kwargs):
|
||||
|
||||
media_card = MediaCard()
|
||||
media_card.anime_id = anime_item["id"]
|
||||
media_card.anime_id = anime_id = anime_item["id"]
|
||||
|
||||
# TODO: ADD language preference
|
||||
if anime_item["title"]["english"]:
|
||||
if anime_item["title"].get("english"):
|
||||
media_card.title = anime_item["title"]["english"]
|
||||
else:
|
||||
media_card.title = anime_item["title"]["romaji"]
|
||||
@@ -175,7 +172,7 @@ class MediaCardDataLoader(object):
|
||||
if anime_item.get("genres"):
|
||||
media_card.genres = anilist_data_helper.format_list_data_with_comma(anime_item["genres"])
|
||||
|
||||
if anime_item["id"] in user_anime_list:
|
||||
if anime_id in user_anime_list:
|
||||
media_card.is_in_my_list = True
|
||||
|
||||
if anime_item["averageScore"]:
|
||||
@@ -188,7 +185,7 @@ class MediaCardDataLoader(object):
|
||||
# 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("anime",yt_watch_link) # type: ignore # trailer_url is the yt_watch_link
|
||||
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])
|
||||
@@ -204,7 +201,7 @@ class MediaCardDataLoader(object):
|
||||
'current_anime':anime_item["id"],
|
||||
'kwargs': kwargs})
|
||||
if not kwargs.get('nocache', False):
|
||||
Cache.append('anime',yt_watch_link, (False,False))
|
||||
Cache.append('yt_stream_links.anime',yt_watch_link, (False,False))
|
||||
self._start_wanted = True
|
||||
self._trigger_update()
|
||||
return media_card
|
||||
@@ -235,4 +232,4 @@ class LoaderThreadPool(MediaCardDataLoader):
|
||||
|
||||
MediaCardLoader = LoaderThreadPool()
|
||||
Logger.info('MediaCardLoader: using a thread pool of {} workers'.format(
|
||||
MediaCardLoader.num_workers))
|
||||
MediaCardLoader._num_workers))
|
||||
@@ -5,7 +5,7 @@ Contains Helper functions to read and write the user data files
|
||||
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
from datetime import date,datetime
|
||||
|
||||
from kivy.logger import Logger
|
||||
|
||||
today = date.today()
|
||||
now = datetime.now()
|
||||
@@ -15,28 +15,56 @@ yt_cache = JsonStore("yt_cache.json")
|
||||
|
||||
|
||||
# Get the user data
|
||||
def get_user_animelist():
|
||||
def get_user_anime_list()->list:
|
||||
try:
|
||||
return user_data.get("my_list")["list"] # returns a list of anime ids
|
||||
except:
|
||||
return user_data.get("user_anime_list")["user_anime_list"] # returns a list of anime ids
|
||||
except Exception as e:
|
||||
Logger.warning(f"User Data:Read failure:{e}")
|
||||
return []
|
||||
|
||||
def update_user_anime_list(new_list:list):
|
||||
def update_user_anime_list(updated_list:list):
|
||||
try:
|
||||
user_data.put("my_list",list=set(new_list))
|
||||
except:
|
||||
pass
|
||||
updated_list_ = list(set(updated_list))
|
||||
user_data.put("user_anime_list",user_anime_list=updated_list_)
|
||||
except Exception as e:
|
||||
Logger.warning(f"User Data:Update failure:{e}")
|
||||
|
||||
def get_anime_trailer_cache():
|
||||
# Get the user data
|
||||
def get_user_downloads()->list:
|
||||
try:
|
||||
name_of_yt_cache = f"{today}{0 if now.hour>=12 else 1}"
|
||||
return yt_cache["yt_stream_links"][f"{name_of_yt_cache}"]
|
||||
except:
|
||||
return user_data.get("user_downloads")["user_downloads"] # returns a list of anime ids
|
||||
except Exception as e:
|
||||
Logger.warning(f"User Data:Read failure:{e}")
|
||||
return []
|
||||
|
||||
def update_user_downloads(updated_list:list):
|
||||
try:
|
||||
user_data.put("user_downloads",user_downloads=list(set(updated_list)))
|
||||
except Exception as e:
|
||||
Logger.warning(f"User Data:Update failure:{e}")
|
||||
|
||||
|
||||
# Yt persistent anime trailer cache
|
||||
t = 1
|
||||
if now.hour<=6:
|
||||
t = 1
|
||||
elif now.hour<=12:
|
||||
t = 2
|
||||
elif now.hour<=18:
|
||||
t = 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:
|
||||
name_of_yt_cache = f"{today}{0 if now.hour>=12 else 1}"
|
||||
yt_cache.put("yt_stream_links",**{f"{name_of_yt_cache}":yt_stream_links})
|
||||
except:
|
||||
pass
|
||||
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}")
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
MDIconButton:
|
||||
icon: "arrow-left"
|
||||
on_release:
|
||||
root.manager_screens.current = root.manager_screens.previous()
|
||||
root.manager_screens.current = root.caller_screen_name
|
||||
MDScrollView:
|
||||
size_hint:1,1
|
||||
MDBoxLayout:
|
||||
@@ -57,4 +57,4 @@
|
||||
id:anime_characters
|
||||
AnimeReviews:
|
||||
id:anime_reviews
|
||||
MDBoxLayout:
|
||||
BoxLayout:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from datetime import datetime
|
||||
|
||||
from kivy.properties import ObjectProperty,DictProperty
|
||||
from kivy.properties import ObjectProperty,DictProperty,StringProperty
|
||||
|
||||
from Utility import anilist_data_helper
|
||||
from View.base_screen import BaseScreenView
|
||||
@@ -8,6 +8,7 @@ from .components import (AnimeHeader,AnimeSideBar,AnimeDescription,AnimeReviews,
|
||||
|
||||
|
||||
class AnimeScreenView(BaseScreenView):
|
||||
caller_screen_name = StringProperty()
|
||||
header:AnimeHeader = ObjectProperty()
|
||||
side_bar:AnimeSideBar = ObjectProperty()
|
||||
rankings_bar:RankingsBar = ObjectProperty()
|
||||
@@ -15,8 +16,9 @@ class AnimeScreenView(BaseScreenView):
|
||||
anime_characters:AnimeCharacters = ObjectProperty()
|
||||
anime_reviews:AnimeReviews = ObjectProperty()
|
||||
data = DictProperty()
|
||||
|
||||
def update_layout(self,data):
|
||||
anime_id = 0
|
||||
def update_layout(self,data:dict,caller_screen_name:str):
|
||||
self.caller_screen_name = caller_screen_name
|
||||
self.data = data
|
||||
# uitlity functions
|
||||
|
||||
@@ -114,3 +116,5 @@ class AnimeScreenView(BaseScreenView):
|
||||
|
||||
DownloadAnimeDialog(self.data).open()
|
||||
|
||||
def add_to_user_anime_list(self,*args):
|
||||
self.app.add_anime_to_user_anime_list(self.model.anime_id)
|
||||
@@ -4,9 +4,12 @@
|
||||
spacing:"10dp"
|
||||
pos_hint: {'center_x': 0.5}
|
||||
MDButton:
|
||||
on_press: print("presed")
|
||||
on_press:
|
||||
root.screen.add_to_user_anime_list()
|
||||
add_to_user_list_label.text = "Added to MyAnimeList"
|
||||
MDButtonText:
|
||||
text:"Add to MyList"
|
||||
id:add_to_user_list_label
|
||||
text:"Add to MyAnimeList"
|
||||
MDButton:
|
||||
on_press:
|
||||
if root.screen:root.screen.stream_anime_with_custom_cmds_dialog()
|
||||
|
||||
@@ -6,6 +6,7 @@ class DownloadAnimeDialog(ThemableBehavior,StencilBehavior,CommonElevationBehavi
|
||||
def __init__(self,data,**kwargs):
|
||||
super(DownloadAnimeDialog,self).__init__(**kwargs)
|
||||
self.data = data
|
||||
self.anime_id = self.data["id"]
|
||||
if title:=data["title"].get("romaji"):
|
||||
self.ids.title_field.text = title
|
||||
elif title:=data["title"].get("english"):
|
||||
@@ -22,4 +23,4 @@ class DownloadAnimeDialog(ThemableBehavior,StencilBehavior,CommonElevationBehavi
|
||||
default_cmds["quality"] = quality
|
||||
|
||||
# print(title,episodes_range,latest,quality)
|
||||
app.download_anime(default_cmds)
|
||||
app.download_anime(self.anime_id,default_cmds)
|
||||
@@ -23,13 +23,11 @@
|
||||
max_lines:0
|
||||
shorten:False
|
||||
bold:True
|
||||
markup:True
|
||||
font_style: "Label"
|
||||
role: "large"
|
||||
md_bg_color:self.theme_cls.secondaryContainerColor
|
||||
padding:"10dp"
|
||||
|
||||
|
||||
<AnimeSideBar>:
|
||||
size_hint_x: None
|
||||
width: dp(300)
|
||||
@@ -52,7 +50,6 @@
|
||||
|
||||
MDButtonText:
|
||||
text:"Watch on Animdl"
|
||||
|
||||
FitBoxLayout:
|
||||
SideBarHeaderLabel:
|
||||
text:"Alternative Titles"
|
||||
@@ -62,7 +59,6 @@
|
||||
text: "[color={}]English:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.alternative_titles["english"])
|
||||
SideBarLabel:
|
||||
text: "[color={}]Japanese:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.alternative_titles["japanese"])
|
||||
|
||||
FitBoxLayout:
|
||||
SideBarHeaderLabel:
|
||||
text:"Information"
|
||||
@@ -105,4 +101,3 @@
|
||||
SideBarHeaderLabel:
|
||||
text:"External Links"
|
||||
BoxLayout:
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
bold:True
|
||||
<DownloadsScreenView>
|
||||
md_bg_color: self.theme_cls.backgroundColor
|
||||
# main_container:main_container
|
||||
main_container:main_container
|
||||
download_progress_label:download_progress_label
|
||||
progress_bar:progress_bar
|
||||
MDBoxLayout:
|
||||
@@ -23,13 +23,13 @@
|
||||
orientation: 'vertical'
|
||||
SearchBar:
|
||||
MDScrollView:
|
||||
size_hint:1,1
|
||||
size_hint:.95,1
|
||||
MDGridLayout:
|
||||
padding: "75dp","50dp","10dp","100dp"
|
||||
spacing:"40dp"
|
||||
id:main_container
|
||||
cols:5
|
||||
adaptive_height:True
|
||||
MDLabel:
|
||||
text:"Dowloads"
|
||||
MDBoxLayout:
|
||||
size_hint_y:None
|
||||
height:"40dp"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from kivy.clock import Clock
|
||||
from kivy.properties import ObjectProperty
|
||||
from kivy.utils import format_bytes_to_human
|
||||
|
||||
@@ -12,3 +13,9 @@ class DownloadsScreenView(BaseScreenView):
|
||||
percentage_completion = (current_bytes_downloaded/total_bytes)*100
|
||||
self.progress_bar.value= max(min(percentage_completion,100),0)
|
||||
self.download_progress_label.text = f"Downloading: {episode_info['anime_title']} - {episode_info['episode']} ({format_bytes_to_human(current_bytes_downloaded)}/{format_bytes_to_human(total_bytes)})"
|
||||
|
||||
# def on_enter(self):
|
||||
# Clock.schedule_once(lambda _:self.controller.requested_update_my_list_screen())
|
||||
|
||||
def update_layout(self,widget):
|
||||
self.user_anime_list_container.add_widget(widget)
|
||||
|
||||
@@ -21,9 +21,3 @@
|
||||
size_hint_y:None
|
||||
height:max(self.minimum_height,p.height,1800)
|
||||
|
||||
MDBoxLayout:
|
||||
size_hint_y:None
|
||||
height:self.minimum_height
|
||||
MDLabel:
|
||||
text: "By BeneX"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<MyListScreenView>
|
||||
md_bg_color: self.theme_cls.backgroundColor
|
||||
my_list_container:my_list_container
|
||||
user_anime_list_container:user_anime_list_container
|
||||
MDBoxLayout:
|
||||
size_hint:1,1
|
||||
NavRail:
|
||||
@@ -9,18 +9,18 @@
|
||||
anchor_y: 'top'
|
||||
padding:"10dp"
|
||||
size_hint:1,1
|
||||
|
||||
MDBoxLayout:
|
||||
spacing:"40dp"
|
||||
orientation: 'vertical'
|
||||
id:p
|
||||
size_hint:1,1
|
||||
size_hint:.95,1
|
||||
SearchBar:
|
||||
MDScrollView:
|
||||
pos_hint:{"center_x":.5}
|
||||
size_hint:1,1
|
||||
MDGridLayout:
|
||||
spacing: '10dp'
|
||||
padding: "75dp","50dp","10dp","100dp"
|
||||
id:my_list_container
|
||||
spacing: '40dp'
|
||||
padding: "100dp","50dp","10dp","200dp"
|
||||
id:user_anime_list_container
|
||||
cols:5
|
||||
size_hint_y:None
|
||||
height:self.minimum_height
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from kivy.properties import ObjectProperty,StringProperty,DictProperty
|
||||
from kivy.clock import Clock
|
||||
from View.base_screen import BaseScreenView
|
||||
|
||||
|
||||
class MyListScreenView(BaseScreenView):
|
||||
my_list_container = ObjectProperty()
|
||||
user_anime_list_container = ObjectProperty()
|
||||
def model_is_changed(self) -> None:
|
||||
"""
|
||||
Called whenever any change has occurred in the data model.
|
||||
@@ -11,8 +12,9 @@ class MyListScreenView(BaseScreenView):
|
||||
according to these changes.
|
||||
"""
|
||||
|
||||
def on_enter(self):
|
||||
Clock.schedule_once(lambda _:self.controller.requested_update_my_list_screen())
|
||||
|
||||
def update_layout(self,widget):
|
||||
pass
|
||||
def add_pagination(self,pagination_info):
|
||||
pass
|
||||
self.user_anime_list_container.add_widget(widget)
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
size_hint_x:None
|
||||
width: dp(250)
|
||||
MDLabel:
|
||||
md_bg_color:self.theme_cls.secondaryContainerColor
|
||||
adaptive_height:True
|
||||
halign:"center"
|
||||
max_lines:0
|
||||
@@ -59,7 +60,6 @@
|
||||
font_style: "Label"
|
||||
role: "large"
|
||||
text:"Trending"
|
||||
md_bg_color:self.theme_cls.secondaryContainerColor
|
||||
padding:"10dp"
|
||||
MDScrollView:
|
||||
TrendingAnimeSideBar:
|
||||
|
||||
@@ -6,7 +6,3 @@ 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
|
||||
@@ -104,7 +104,7 @@
|
||||
icon: "play-circle"
|
||||
on_press:
|
||||
root.dismiss()
|
||||
app.show_anime_screen(root.caller.anime_id)
|
||||
app.show_anime_screen(root.caller.anime_id,root.caller.screen.name)
|
||||
TooltipMDIconButton:
|
||||
tooltip_text:"Add to your anime list"
|
||||
icon: "plus-circle" if not(root.caller.is_in_my_list) else "check-circle"
|
||||
|
||||
@@ -8,6 +8,7 @@ from kivymd.uix.behaviors import HoverBehavior
|
||||
from .components import MediaPopup
|
||||
|
||||
class MediaCard(ButtonBehavior,HoverBehavior,MDBoxLayout):
|
||||
screen = ObjectProperty()
|
||||
anime_id = NumericProperty()
|
||||
title = StringProperty()
|
||||
is_play = ObjectProperty()
|
||||
@@ -28,7 +29,6 @@ class MediaCard(ButtonBehavior,HoverBehavior,MDBoxLayout):
|
||||
stars = ListProperty([0,0,0,0,0,0])
|
||||
cover_image_url = StringProperty()
|
||||
preview_image = StringProperty()
|
||||
screen = ObjectProperty()
|
||||
has_trailer_color = ListProperty([1,1,1,0])
|
||||
|
||||
|
||||
@@ -75,10 +75,12 @@ class MediaCard(ButtonBehavior,HoverBehavior,MDBoxLayout):
|
||||
popup.open(self)
|
||||
|
||||
# ---------------respond to user actions and call appropriate model-------------------------
|
||||
def on_is_in_my_list(self,instance,value):
|
||||
|
||||
def on_is_in_my_list(self,instance,in_user_anime_list):
|
||||
if self.screen:
|
||||
self.screen.controller.update_my_list(self.anime_id,value)
|
||||
if in_user_anime_list:
|
||||
self.screen.app.add_anime_to_user_anime_list(self.anime_id)
|
||||
else:
|
||||
self.screen.app.remove_anime_from_user_anime_list(self.anime_id)
|
||||
|
||||
def on_trailer_url(self,*args):
|
||||
pass
|
||||
|
||||
@@ -38,6 +38,7 @@ class AniList:
|
||||
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]|None=None,
|
||||
popularity_greater:int|None=None,
|
||||
popularity_lesser:int|None=None,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
optional_variables = "\
|
||||
$page:Int,\
|
||||
$sort:[MediaSort],\
|
||||
$id_in:[Int],\
|
||||
$genre_in:[String],\
|
||||
$genre_not_in:[String],\
|
||||
$tag_in:[String],\
|
||||
@@ -29,6 +30,7 @@ query($query:String,%s){
|
||||
}
|
||||
media(
|
||||
search:$query,
|
||||
id_in:$id_in,
|
||||
genre_in:$genre_in,
|
||||
genre_not_in:$genre_not_in,
|
||||
tag_in:$tag_in,
|
||||
|
||||
@@ -9,6 +9,9 @@ from fuzzywuzzy import fuzz
|
||||
|
||||
broken_link_pattern = r"https://tools.fast4speed.rsvp/\w*"
|
||||
|
||||
def path_parser(path:str)->str:
|
||||
return path.replace(":","").replace("/", "").replace("\\","").replace("\"","").replace("'","").replace("<","").replace(">","").replace("|","").replace("?","").replace(".","").replace("*","")
|
||||
|
||||
# TODO: WRITE Docs for each method
|
||||
class AnimdlApi:
|
||||
@classmethod
|
||||
@@ -52,13 +55,15 @@ class AnimdlApi:
|
||||
failed_downloads = []
|
||||
successful_downloads = []
|
||||
anime_title,episodes_to_download = data
|
||||
anime_title = anime_title.capitalize()
|
||||
anime_title:str = anime_title.capitalize()
|
||||
|
||||
if not episodes_to_download:
|
||||
return False,None
|
||||
|
||||
# determine download location
|
||||
parsed_anime_title = anime_title.replace(":","").replace("/", "").replace("\\","")
|
||||
|
||||
parsed_anime_title = path_parser(anime_title)
|
||||
|
||||
download_location = os.path.join(output_path,parsed_anime_title)
|
||||
if not os.path.exists(download_location):
|
||||
os.mkdir(download_location)
|
||||
@@ -99,7 +104,8 @@ class AnimdlApi:
|
||||
|
||||
# determine episode_title
|
||||
if title:=stream.get("title"):
|
||||
episode_title = f"{episode_title} - {title}"
|
||||
episode_title = f"{episode_title} - {path_parser(title)}"
|
||||
|
||||
parsed_episode_title = episode_title.replace(":","").replace("/", "").replace("\\","")
|
||||
episode_download_location = os.path.join(download_location,parsed_episode_title)
|
||||
if not os.path.exists(episode_download_location):
|
||||
|
||||
46
app/main.py
46
app/main.py
@@ -14,6 +14,7 @@ from kivy.config import Config
|
||||
# Config.write()
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.logger import Logger
|
||||
from kivy.uix.screenmanager import ScreenManager,FadeTransition
|
||||
from kivy.uix.settings import SettingsWithSidebar,Settings
|
||||
|
||||
@@ -24,10 +25,9 @@ from View.screens import screens
|
||||
from libs.animdl.animdl_api import AnimdlApi
|
||||
from Utility import themes_available,show_notification,user_data_helper
|
||||
|
||||
# TODO: ADD logging across the codebase
|
||||
|
||||
# Ensure the user data fields exist
|
||||
if not(user_data_helper.user_data.exists("my_list")):
|
||||
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")):
|
||||
@@ -65,27 +65,32 @@ class AniXStreamApp(MDApp):
|
||||
self.worker_thread = Thread(target=self.worker,args=(self.queue,))
|
||||
self.worker_thread.daemon = True
|
||||
self.worker_thread.start()
|
||||
Logger.info("AniXStream:Successfully started background tasks worker")
|
||||
|
||||
# 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()
|
||||
Logger.info("AniXStream:Successfully started download worker")
|
||||
|
||||
def build(self) -> ScreenManager:
|
||||
self.settings_cls = SettingsWithSidebar
|
||||
|
||||
self.generate_application_screens()
|
||||
|
||||
if config:=self.config:
|
||||
if theme_color:=config.get("Preferences","theme_color"):
|
||||
self.theme_cls.primary_palette = theme_color
|
||||
if theme_style:=config.get("Preferences","theme_style"):
|
||||
self.theme_cls.theme_style = theme_style
|
||||
|
||||
self.anime_screen = self.manager_screens.get_screen("anime screen")
|
||||
self.search_screen = self.manager_screens.get_screen("search screen")
|
||||
self.download_screen = self.manager_screens.get_screen("downloads screen")
|
||||
return self.manager_screens
|
||||
|
||||
def on_start(self,*args):
|
||||
super().on_start(*args)
|
||||
pass
|
||||
|
||||
def generate_application_screens(self) -> None:
|
||||
for i, name_screen in enumerate(screens.keys()):
|
||||
@@ -103,7 +108,6 @@ class AniXStreamApp(MDApp):
|
||||
"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")
|
||||
@@ -115,6 +119,7 @@ class AniXStreamApp(MDApp):
|
||||
if value in themes_available:
|
||||
self.theme_cls.primary_palette = value
|
||||
else:
|
||||
Logger.warning("Settings:An invalid theme has been entered and will be ignored")
|
||||
config.set("Preferences","theme_color","Cyan")
|
||||
config.write()
|
||||
case "theme_style":
|
||||
@@ -123,6 +128,7 @@ class AniXStreamApp(MDApp):
|
||||
def on_stop(self):
|
||||
if self.animdl_streaming_subprocess:
|
||||
self.animdl_streaming_subprocess.terminate()
|
||||
Logger.info("Animdl:Successfully terminated existing animdl subprocess")
|
||||
|
||||
# custom methods
|
||||
# TODO: may move theme to a personalized class
|
||||
@@ -131,9 +137,24 @@ class AniXStreamApp(MDApp):
|
||||
self.manager_screens.current = "search screen"
|
||||
self.search_screen.handle_search_for_anime(search_field,**kwargs)
|
||||
|
||||
def show_anime_screen(self,id):
|
||||
def add_anime_to_user_anime_list(self,id:int):
|
||||
updated_list = user_data_helper.get_user_anime_list()
|
||||
updated_list.append(id)
|
||||
user_data_helper.update_user_anime_list(updated_list)
|
||||
|
||||
def remove_anime_from_user_anime_list(self,id:int):
|
||||
updated_list = user_data_helper.get_user_anime_list()
|
||||
if updated_list.count(id): updated_list.remove(id)
|
||||
user_data_helper.update_user_anime_list(updated_list)
|
||||
|
||||
def add_anime_to_user_downloads_list(self,id:int):
|
||||
updated_list = user_data_helper.get_user_downloads()
|
||||
updated_list.append(id)
|
||||
user_data_helper.get_user_downloads(updated_list)
|
||||
|
||||
def show_anime_screen(self,id:int,caller_screen_name:str):
|
||||
self.manager_screens.current = "anime screen"
|
||||
self.anime_screen.controller.update_anime_view(id)
|
||||
self.anime_screen.controller.update_anime_view(id,caller_screen_name)
|
||||
|
||||
def watch_on_allanime(self,title_):
|
||||
"""
|
||||
@@ -146,24 +167,31 @@ class AniXStreamApp(MDApp):
|
||||
title,link = anime
|
||||
parsed_link = f"https://allmanga.to/bangumi/{link.split('/')[-1]}"
|
||||
else:
|
||||
Logger.error(f"AniXStream:Failed to open {title} in browser on allanime site")
|
||||
show_notification("Failure",f"Failed to open {title} in browser on allanime site")
|
||||
if webbrowser.open(parsed_link):
|
||||
Logger.info(f"AniXStream:Successfully opened {title} in browser allanime site")
|
||||
show_notification("Success",f"Successfully opened {title} in browser allanime site")
|
||||
else:
|
||||
Logger.error(f"AniXStream:Failed to open {title} in browser on allanime site")
|
||||
show_notification("Failure",f"Failed to open {title} in browser on allanime site")
|
||||
|
||||
def download_anime_complete(self,successful_downloads:list,failed_downloads:list,anime_title:str):
|
||||
show_notification(f"Finished Dowloading {anime_title}",f"There were {len(successful_downloads)} successful downloads and {len(failed_downloads)} failed downloads")
|
||||
Logger.info(f"Downloader:Finished Downloading {anime_title} and there were {len(failed_downloads)} failed downloads")
|
||||
|
||||
def download_anime(self,default_cmds):
|
||||
def download_anime(self,anime_id:int,default_cmds:dict):
|
||||
show_notification("New Download Task Queued",f"{default_cmds.get('title')} has been queued for downloading")
|
||||
|
||||
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)
|
||||
output_path = self.config.get("Preferences","downloads_dir")
|
||||
if episodes_range:=default_cmds.get("episodes_range"):
|
||||
download_task =lambda: AnimdlApi.download_anime_by_title(default_cmds["title"],on_progress,self.download_anime_complete,output_path,episodes_range) # ,default_cmds["quality"]
|
||||
download_task =lambda: AnimdlApi.download_anime_by_title(default_cmds['title'],on_progress,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,self.download_anime_complete,output_path) # ,default_cmds.get("quality")
|
||||
self.downloads_queue.put(download_task)
|
||||
@@ -199,7 +227,7 @@ class AniXStreamApp(MDApp):
|
||||
else:
|
||||
stream_func = lambda:self.stream_anime_with_custom_input_cmds(*custom_options)
|
||||
self.queue.put(stream_func)
|
||||
|
||||
Logger.info(f"Animdl:Successfully started to stream {title}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"my_list": {"list": [1535, 20605, 21519, 21, 5114, 133007, 166710, 116674, 11061, 21745, 9253, 153406, 166613, 133845, 143103, 20996, 162804, 125367, 21827]}}
|
||||
{"user_anime_list": {"user_anime_list": [116674, 21640, 17641, 117612, 269, 6702, 20657, 20626, 8247, 107226, 19163, 15583]}}
|
||||
Reference in New Issue
Block a user