mirror of
https://github.com/Benexl/FastAnime.git
synced 2026-02-05 03:16:50 -08:00
style:Formatted codebase to pep8
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -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/
|
||||
|
||||
14
.project/changes_outline.md
Normal file
14
.project/changes_outline.md
Normal file
@@ -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
|
||||
@@ -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
|
||||
from .home_screen import HomeScreenController
|
||||
from .my_list_screen import MyListScreenController
|
||||
from .search_screen import SearchScreenController
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)}",
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
from .home_screen import HomeScreenModel
|
||||
from .my_list_screen import MyListScreenModel
|
||||
from .search_screen import SearchScreenModel
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -5,4 +5,3 @@ class DownloadsScreenModel(BaseScreenModel):
|
||||
"""
|
||||
Handles the download screen logic
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from .base_model import BaseScreenModel
|
||||
|
||||
|
||||
|
||||
class HelpScreenModel(BaseScreenModel):
|
||||
"""
|
||||
Handles the help screen logic
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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']
|
||||
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",
|
||||
]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from kivy.properties import ObjectProperty
|
||||
|
||||
from kivymd.uix.gridlayout import MDGridLayout
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from kivy.properties import StringProperty
|
||||
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from kivy.properties import StringProperty
|
||||
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from kivy.properties import DictProperty
|
||||
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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}"
|
||||
self.episodes_to_download = f"Episodes: {episodes}"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from kivy.properties import ObjectProperty, NumericProperty
|
||||
|
||||
from kivy.properties import NumericProperty, ObjectProperty
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from kivy.uix.modalview import ModalView
|
||||
|
||||
|
||||
class AnimdlDialogPopup(ModalView):
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -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"
|
||||
# self.state = "pause"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
from .anilist_data_schema import AnilistBaseMediaDataSchema
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,4 +15,4 @@ class NoValidAnimeStreamsException(Exception):
|
||||
|
||||
|
||||
class InvalidAnimdlCommandsException(Exception):
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
4
pyrightconfig.json
Normal file
4
pyrightconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"venv": ".venv",
|
||||
"venvPath": "."
|
||||
}
|
||||
Reference in New Issue
Block a user