mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-12 07:40:41 -08:00
doc and style: formatted the whole codebase to pep8 plus added documentation where necessary
This commit is contained in:
3
app/.vscode/settings.json
vendored
3
app/.vscode/settings.json
vendored
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"python.analysis.typeCheckingMode": "basic"
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
"python.analysis.autoImportCompletions": true
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ Cache.register("data.anime", limit=20, timeout=600)
|
||||
|
||||
|
||||
class AnimeScreenController:
|
||||
"""The controller for the anime screen
|
||||
"""
|
||||
def __init__(self, model: AnimeScreenModel):
|
||||
self.model = model
|
||||
self.view = AnimeScreenView(controller=self, model=self.model)
|
||||
@@ -17,6 +19,12 @@ 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
|
||||
|
||||
Args:
|
||||
id (int): the anilst id of the anime
|
||||
caller_screen_name (str): the screen thats calling this method; used internally to switch back to this screen
|
||||
"""
|
||||
if self.model.anime_id != id:
|
||||
if cached_anime_data := Cache.get("data.anime", f"{id}"):
|
||||
data = cached_anime_data
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
|
||||
from inspect import isgenerator
|
||||
from View import CrashLogScreenView
|
||||
from Model import CrashLogScreenModel
|
||||
from View.components import MediaCardsContainer
|
||||
from Utility import show_notification
|
||||
from kivy.clock import Clock
|
||||
|
||||
|
||||
|
||||
class CrashLogScreenController:
|
||||
"""The crash log screen controller
|
||||
"""
|
||||
def __init__(self, model:CrashLogScreenModel):
|
||||
self.model = model
|
||||
self.view = CrashLogScreenView(controller=self, model=self.model)
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
|
||||
from inspect import isgenerator
|
||||
from View import DownloadsScreenView
|
||||
from Model import DownloadsScreenModel
|
||||
from View.components import MediaCardsContainer
|
||||
from Utility import show_notification
|
||||
from kivy.clock import Clock
|
||||
|
||||
|
||||
class DownloadsScreenController:
|
||||
"""The controller for the download screen
|
||||
"""
|
||||
def __init__(self, model:DownloadsScreenModel):
|
||||
self.model = model
|
||||
self.view = DownloadsScreenView(controller=self, model=self.model)
|
||||
# self.update_anime_view()
|
||||
|
||||
def get_view(self) -> DownloadsScreenView:
|
||||
return self.view
|
||||
|
||||
# def requested_update_my_list_screen(self):
|
||||
# user_anime_list = user_data_helper.get_user_anime_list()
|
||||
# if animes_to_add:=difference(user_anime_list,self.model.already_in_user_anime_list):
|
||||
# Logger.info("My List Screen:User anime list change;updating screen")
|
||||
# anime_cards = self.model.update_my_anime_list_view(animes_to_add)
|
||||
# self.model.already_in_user_anime_list = user_anime_list
|
||||
# if isgenerator(anime_cards):
|
||||
# for result_card in anime_cards:
|
||||
# result_card.screen = self.view
|
||||
# self.view.update_layout(result_card)
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
|
||||
from inspect import isgenerator
|
||||
from View import HelpScreenView
|
||||
from Model import HelpScreenModel
|
||||
from View.components import MediaCardsContainer
|
||||
from Utility import show_notification
|
||||
from kivy.clock import Clock
|
||||
|
||||
|
||||
class HelpScreenController:
|
||||
"""The help screen controller
|
||||
"""
|
||||
def __init__(self, model:HelpScreenModel):
|
||||
self.model = model
|
||||
self.view = HelpScreenView(controller=self, model=self.model)
|
||||
# self.update_anime_view()
|
||||
|
||||
def get_view(self) -> HelpScreenView:
|
||||
return self.view
|
||||
|
||||
@@ -13,7 +13,7 @@ from Utility import show_notification
|
||||
# TODO:Move the update home screen to homescreen.py
|
||||
class HomeScreenController:
|
||||
"""
|
||||
The `MainScreenController` class represents a controller implementation.
|
||||
The `HomeScreenController` class represents a controller implementation.
|
||||
Coordinates work of the view with the model.
|
||||
The controller implements the strategy pattern. The controller connects to
|
||||
the view to control its actions.
|
||||
@@ -25,6 +25,7 @@ class HomeScreenController:
|
||||
self.view = HomeScreenView(controller=self, model=self.model)
|
||||
if self.view.app.config.get("Preferences","is_startup_anime_enable")=="1": # type: ignore
|
||||
Clock.schedule_once(lambda _:self.populate_home_screen())
|
||||
|
||||
def get_view(self) -> HomeScreenView:
|
||||
return self.view
|
||||
|
||||
@@ -108,16 +109,13 @@ class HomeScreenController:
|
||||
|
||||
def populate_home_screen(self):
|
||||
self.populate_errors = []
|
||||
self.trending_anime()
|
||||
self.highest_scored_anime()
|
||||
self.popular_anime()
|
||||
self.favourite_anime()
|
||||
self.recently_updated_anime()
|
||||
self.upcoming_anime()
|
||||
Clock.schedule_once(lambda _:self.trending_anime())
|
||||
Clock.schedule_once(lambda _:self.highest_scored_anime())
|
||||
Clock.schedule_once(lambda _:self.popular_anime())
|
||||
Clock.schedule_once(lambda _: self.favourite_anime())
|
||||
Clock.schedule_once(lambda _:self.recently_updated_anime())
|
||||
Clock.schedule_once(lambda _:self.upcoming_anime())
|
||||
|
||||
if self.populate_errors:
|
||||
show_notification(f"Failed to fetch all home screen data",f"Theres probably a problem with your internet connection or anilist servers are down.\nFailed include:{', '.join(self.populate_errors)}")
|
||||
|
||||
def update_my_list(self,*args):
|
||||
self.model.update_user_anime_list(*args)
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
from inspect import isgenerator
|
||||
|
||||
from kivy.logger import Logger
|
||||
from kivy.clock import Clock
|
||||
# from kivy.clock import Clock
|
||||
from kivy.utils import difference
|
||||
|
||||
from View import MyListScreenView
|
||||
from Model import MyListScreenModel
|
||||
from Utility import show_notification,user_data_helper
|
||||
from Utility import user_data_helper
|
||||
|
||||
class MyListScreenController:
|
||||
"""
|
||||
The `MainScreenController` class represents a controller implementation.
|
||||
The `MyListScreenController` class represents a controller implementation.
|
||||
Coordinates work of the view with the model.
|
||||
The controller implements the strategy pattern. The controller connects to
|
||||
the view to control its actions.
|
||||
@@ -20,6 +20,7 @@ class MyListScreenController:
|
||||
self.model = model
|
||||
self.view = MyListScreenView(controller=self, model=self.model)
|
||||
self.requested_update_my_list_screen()
|
||||
|
||||
def get_view(self) -> MyListScreenView:
|
||||
return self.view
|
||||
|
||||
@@ -33,5 +34,3 @@ class MyListScreenController:
|
||||
for result_card in anime_cards:
|
||||
result_card.screen = self.view
|
||||
self.view.update_layout(result_card)
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ from Model import SearchScreenModel
|
||||
|
||||
|
||||
class SearchScreenController:
|
||||
"""The search screen controller
|
||||
"""
|
||||
|
||||
def __init__(self, model: SearchScreenModel):
|
||||
self.model = model
|
||||
@@ -17,6 +19,8 @@ class SearchScreenController:
|
||||
return self.view
|
||||
|
||||
def update_trending_anime(self):
|
||||
"""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()
|
||||
@@ -37,7 +41,7 @@ class SearchScreenController:
|
||||
Clock.schedule_once(
|
||||
lambda _: self.view.update_pagination(self.model.pagination_info)
|
||||
)
|
||||
Clock.schedule_once(lambda _: self.update_trending_anime())
|
||||
self.update_trending_anime()
|
||||
else:
|
||||
Logger.error(f"Home Screen:Failed to search for {anime_title}")
|
||||
self.view.is_searching = False
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import json
|
||||
import os
|
||||
from Model.base_model import BaseScreenModel
|
||||
from libs.anilist import AniList
|
||||
from Utility.media_card_loader import MediaCardLoader
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
|
||||
user_data= JsonStore("user_data.json")
|
||||
class AnimeScreenModel(BaseScreenModel):
|
||||
"""the Anime screen model
|
||||
"""
|
||||
data = {}
|
||||
anime_id = 0
|
||||
|
||||
def media_card_generator(self):
|
||||
for anime_item in self.data["data"]["Page"]["media"]:
|
||||
yield MediaCardLoader.media_card(anime_item)
|
||||
|
||||
def get_anime_data(self,id:int):
|
||||
return AniList.get_anime(id)
|
||||
|
||||
|
||||
@@ -5,16 +5,4 @@ class DownloadsScreenModel(BaseScreenModel):
|
||||
"""
|
||||
Handles the download screen logic
|
||||
"""
|
||||
|
||||
# already_in_user_anime_list = []
|
||||
# def update_my_anime_list_view(self,not_yet_in_user_anime_list:list,**kwargs):
|
||||
# success,self.data = AniList.search(id_in=not_yet_in_user_anime_list)
|
||||
# if success:
|
||||
# return self.media_card_generator()
|
||||
# else:
|
||||
# show_notification(f"Failed to update my list screen view",self.data["Error"])
|
||||
# return None
|
||||
|
||||
# def media_card_generator(self):
|
||||
# for anime_item in self.data["data"]["Page"]["media"]:
|
||||
# yield MediaCardLoader.media_card(anime_item)
|
||||
|
||||
@@ -1,73 +1,79 @@
|
||||
from Model.base_model import BaseScreenModel
|
||||
from libs.anilist import AniList
|
||||
from Utility.media_card_loader import MediaCardLoader
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
|
||||
user_data= JsonStore("user_data.json")
|
||||
|
||||
|
||||
class HomeScreenModel(BaseScreenModel):
|
||||
"""The home screen model"""
|
||||
|
||||
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)
|
||||
|
||||
return _data_generator()
|
||||
else:
|
||||
return data
|
||||
|
||||
def get_most_favourite_anime(self):
|
||||
success,data = AniList.get_most_favourite()
|
||||
success, data = AniList.get_most_favourite()
|
||||
if success:
|
||||
|
||||
def _data_generator():
|
||||
for anime_item in data["data"]["Page"]["media"]:
|
||||
yield MediaCardLoader.media_card(anime_item)
|
||||
|
||||
return _data_generator()
|
||||
else:
|
||||
return data
|
||||
|
||||
def get_most_recently_updated_anime(self):
|
||||
success,data = AniList.get_most_recently_updated()
|
||||
success, data = AniList.get_most_recently_updated()
|
||||
if success:
|
||||
|
||||
def _data_generator():
|
||||
for anime_item in data["data"]["Page"]["media"]:
|
||||
yield MediaCardLoader.media_card(anime_item)
|
||||
|
||||
return _data_generator()
|
||||
else:
|
||||
return data
|
||||
|
||||
def get_most_popular_anime(self):
|
||||
success,data = AniList.get_most_popular()
|
||||
success, data = AniList.get_most_popular()
|
||||
if success:
|
||||
|
||||
def _data_generator():
|
||||
for anime_item in data["data"]["Page"]["media"]:
|
||||
yield MediaCardLoader.media_card(anime_item)
|
||||
|
||||
return _data_generator()
|
||||
else:
|
||||
return data
|
||||
|
||||
def get_most_scored_anime(self):
|
||||
success,data = AniList.get_most_scored()
|
||||
success, data = AniList.get_most_scored()
|
||||
if success:
|
||||
|
||||
def _data_generator():
|
||||
for anime_item in data["data"]["Page"]["media"]:
|
||||
yield MediaCardLoader.media_card(anime_item)
|
||||
|
||||
return _data_generator()
|
||||
else:
|
||||
return data
|
||||
|
||||
def get_upcoming_anime(self):
|
||||
success,data = AniList.get_upcoming_anime(1)
|
||||
success, data = AniList.get_upcoming_anime(1)
|
||||
if success:
|
||||
|
||||
def _data_generator():
|
||||
for anime_item in data["data"]["Page"]["media"]:
|
||||
yield MediaCardLoader.media_card(anime_item)
|
||||
|
||||
return _data_generator()
|
||||
else:
|
||||
return data
|
||||
def update_user_anime_list(self,anime_id,is_add):
|
||||
my_list:list = user_data.get("my_list")["list"]
|
||||
if is_add:
|
||||
my_list.append(anime_id)
|
||||
elif not(is_add) and my_list:
|
||||
my_list.remove(anime_id)
|
||||
user_data.put("my_list",list=my_list)
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
from typing import Generator
|
||||
|
||||
from Model.base_model import BaseScreenModel
|
||||
from libs.anilist import AniList
|
||||
from Utility.media_card_loader import MediaCardLoader
|
||||
from View.components import MediaCard
|
||||
from Utility import show_notification
|
||||
from Utility import MediaCardLoader, show_notification
|
||||
|
||||
|
||||
class SearchScreenModel(BaseScreenModel):
|
||||
@@ -31,7 +27,3 @@ class SearchScreenModel(BaseScreenModel):
|
||||
for anime_item in self.data["data"]["Page"]["media"]:
|
||||
yield MediaCardLoader.media_card(anime_item)
|
||||
self.pagination_info = self.data["data"]["Page"]["pageInfo"]
|
||||
|
||||
# def extract_pagination_info(self):
|
||||
# pagination_info = None
|
||||
# return pagination_info
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
from datetime import datetime
|
||||
from libs.anilist.anilist_data_schema import AnilistDateObject,AnilistMediaNextAiringEpisode
|
||||
|
||||
from libs.anilist.anilist_data_schema import (
|
||||
AnilistDateObject,
|
||||
AnilistMediaNextAiringEpisode,
|
||||
)
|
||||
|
||||
|
||||
# TODO: Add formating options for the final date
|
||||
@@ -24,7 +28,7 @@ def format_list_data_with_comma(data: list | None):
|
||||
return "None"
|
||||
|
||||
|
||||
def extract_next_airing_episode(airing_episode:AnilistMediaNextAiringEpisode):
|
||||
def extract_next_airing_episode(airing_episode: AnilistMediaNextAiringEpisode):
|
||||
if airing_episode:
|
||||
return f"Episode: {airing_episode['episode']} on {format_anilist_timestamp(airing_episode['airingAt'])}"
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
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']
|
||||
# import time
|
||||
# from datetime import date,datetime
|
||||
# print(datetime.fromtimestamp(1716412399))
|
||||
# print(time.daylight,date.max,date.min)
|
||||
'Yellowgreen']
|
||||
@@ -8,38 +8,47 @@ from kivy.utils import get_hex_from_color
|
||||
def bolden(text: str):
|
||||
return f"[b]{text}[/b]"
|
||||
|
||||
|
||||
def italicize(text: str):
|
||||
return f"[i]{text}[/i]"
|
||||
|
||||
|
||||
def underline(text: str):
|
||||
return f"[u]{text}[/u]"
|
||||
|
||||
|
||||
def strike_through(text: str):
|
||||
return f"[s]{text}[/s]"
|
||||
|
||||
|
||||
def sub_script(text: str):
|
||||
return f"[sub]{text}[/sub]"
|
||||
|
||||
|
||||
def super_script(text: str):
|
||||
return f"[sup]{text}[/sup]"
|
||||
|
||||
|
||||
def color_text(text: str, color: tuple):
|
||||
hex_color = get_hex_from_color(color)
|
||||
return f"[color={hex_color}]{text}[/color]"
|
||||
|
||||
|
||||
def font(text: str, font_name: str):
|
||||
return f"[font={font_name}]{text}[/font]"
|
||||
|
||||
|
||||
def font_family(text: str, family: str):
|
||||
return f"[font_family={family}]{text}[/font_family]"
|
||||
|
||||
|
||||
def font_context(text: str, context: str):
|
||||
return f"[font_context={context}]{text}[/font_context]"
|
||||
|
||||
|
||||
def font_size(text: str, size: int):
|
||||
return f"[size={size}]{text}[/size]"
|
||||
|
||||
|
||||
|
||||
def text_ref(text: str, ref: str):
|
||||
return f"[ref={ref}]{text}[/ref]"
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ for link in yt_stream_links:
|
||||
|
||||
# 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
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
from kivymd.uix.snackbar import MDSnackbar,MDSnackbarText,MDSnackbarSupportingText
|
||||
from kivymd.uix.snackbar import MDSnackbar, MDSnackbarText, MDSnackbarSupportingText
|
||||
from kivy.clock import Clock
|
||||
|
||||
def show_notification(title,details):
|
||||
|
||||
def show_notification(title, details):
|
||||
"""helper function to display notifications
|
||||
|
||||
Args:
|
||||
title (str): the title of your message
|
||||
details (str): the details of your message
|
||||
"""
|
||||
|
||||
def _show(dt):
|
||||
MDSnackbar(
|
||||
MDSnackbarText(
|
||||
text=title,
|
||||
adaptive_height=True,
|
||||
),
|
||||
MDSnackbarSupportingText(
|
||||
text=details,
|
||||
shorten=False,
|
||||
max_lines=0,
|
||||
adaptive_height=True
|
||||
text=details, shorten=False, max_lines=0, adaptive_height=True
|
||||
),
|
||||
duration=5,
|
||||
y="10dp",
|
||||
pos_hint={"bottom": 1,"right":.99},
|
||||
pos_hint={"bottom": 1, "right": 0.99},
|
||||
padding=[0, 0, "8dp", "8dp"],
|
||||
size_hint_x=.4
|
||||
size_hint_x=0.4,
|
||||
).open()
|
||||
Clock.schedule_once(_show,1)
|
||||
|
||||
Clock.schedule_once(_show, 1)
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
Contains Helper functions to read and write the user data files
|
||||
"""
|
||||
|
||||
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
from datetime import date,datetime
|
||||
from datetime import date, datetime
|
||||
from kivy.logger import Logger
|
||||
|
||||
today = date.today()
|
||||
@@ -15,56 +14,68 @@ yt_cache = JsonStore("yt_cache.json")
|
||||
|
||||
|
||||
# Get the user data
|
||||
def get_user_anime_list()->list:
|
||||
def get_user_anime_list() -> list:
|
||||
try:
|
||||
return user_data.get("user_anime_list")["user_anime_list"] # returns a list of anime ids
|
||||
return user_data.get("user_anime_list")[
|
||||
"user_anime_list"
|
||||
] # returns a list of anime ids
|
||||
except Exception as e:
|
||||
Logger.warning(f"User Data:Read failure:{e}")
|
||||
return []
|
||||
|
||||
def update_user_anime_list(updated_list:list):
|
||||
|
||||
def update_user_anime_list(updated_list: list):
|
||||
try:
|
||||
updated_list_ = list(set(updated_list))
|
||||
user_data.put("user_anime_list",user_anime_list=updated_list_)
|
||||
user_data.put("user_anime_list", user_anime_list=updated_list_)
|
||||
except Exception as e:
|
||||
Logger.warning(f"User Data:Update failure:{e}")
|
||||
|
||||
|
||||
# Get the user data
|
||||
def get_user_downloads()->list:
|
||||
def get_user_downloads() -> list:
|
||||
try:
|
||||
return user_data.get("user_downloads")["user_downloads"] # returns a list of anime ids
|
||||
return user_data.get("user_downloads")[
|
||||
"user_downloads"
|
||||
] # returns a list of anime ids
|
||||
except Exception as e:
|
||||
Logger.warning(f"User Data:Read failure:{e}")
|
||||
return []
|
||||
|
||||
def update_user_downloads(updated_list:list):
|
||||
|
||||
def update_user_downloads(updated_list: list):
|
||||
try:
|
||||
user_data.put("user_downloads",user_downloads=list(set(updated_list)))
|
||||
user_data.put("user_downloads", user_downloads=list(set(updated_list)))
|
||||
except Exception as e:
|
||||
Logger.warning(f"User Data:Update failure:{e}")
|
||||
|
||||
|
||||
# Yt persistent anime trailer cache
|
||||
# Yt persistent anime trailer cache
|
||||
t = 1
|
||||
if now.hour<=6:
|
||||
if now.hour <= 6:
|
||||
t = 1
|
||||
elif now.hour<=12:
|
||||
elif now.hour <= 12:
|
||||
t = 2
|
||||
elif now.hour<=18:
|
||||
elif now.hour <= 18:
|
||||
t = 3
|
||||
else:
|
||||
t = 4
|
||||
|
||||
yt_anime_trailer_cache_name = f"{today}{t}"
|
||||
def get_anime_trailer_cache()->list:
|
||||
|
||||
|
||||
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):
|
||||
|
||||
|
||||
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})
|
||||
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,23 +1,26 @@
|
||||
from datetime import datetime
|
||||
# import tempfile
|
||||
import shutil
|
||||
# import os
|
||||
|
||||
# TODO: make it use color_text instead of fixed vals
|
||||
# from .kivy_markup_helper import color_text
|
||||
|
||||
|
||||
# utility functions
|
||||
def write_crash(e:Exception):
|
||||
def write_crash(e: Exception):
|
||||
index = datetime.today()
|
||||
error = f"[b][color=#fa0000][ {index} ]:[/color][/b]\n(\n\n{e}\n\n)\n"
|
||||
try:
|
||||
with open("crashdump.txt","a") as file:
|
||||
with open("crashdump.txt", "a") as file:
|
||||
file.write(error)
|
||||
except:
|
||||
with open("crashdump.txt","w") as file:
|
||||
with open("crashdump.txt", "w") as file:
|
||||
file.write(error)
|
||||
return index
|
||||
|
||||
def move_file(source_path,dest_path):
|
||||
|
||||
def move_file(source_path, dest_path):
|
||||
try:
|
||||
path = shutil.move(source_path,dest_path)
|
||||
return (1,path)
|
||||
path = shutil.move(source_path, dest_path)
|
||||
return (1, path)
|
||||
except Exception as e:
|
||||
return (0,e)
|
||||
return (0, e)
|
||||
|
||||
@@ -19,6 +19,7 @@ from .components import (
|
||||
|
||||
|
||||
class AnimeScreenView(BaseScreenView):
|
||||
"""The anime screen view"""
|
||||
caller_screen_name = StringProperty()
|
||||
header: AnimeHeader = ObjectProperty()
|
||||
side_bar: AnimeSideBar = ObjectProperty()
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
padding:"10dp"
|
||||
orientation:"vertical"
|
||||
StreamDialogHeaderLabel:
|
||||
text:"Stream on Animdl"
|
||||
text:"Stream Anime"
|
||||
StreamDialogLabel:
|
||||
text:"Title"
|
||||
MDTextField:
|
||||
|
||||
@@ -1,20 +1,35 @@
|
||||
from kivy.clock import Clock
|
||||
from kivy.uix.modalview import ModalView
|
||||
|
||||
from kivymd.uix.behaviors import StencilBehavior,CommonElevationBehavior,BackgroundColorBehavior
|
||||
from kivymd.uix.behaviors import (
|
||||
StencilBehavior,
|
||||
CommonElevationBehavior,
|
||||
BackgroundColorBehavior,
|
||||
)
|
||||
from kivymd.theming import ThemableBehavior
|
||||
|
||||
class AnimdlStreamDialog(ThemableBehavior,StencilBehavior,CommonElevationBehavior,BackgroundColorBehavior,ModalView):
|
||||
def __init__(self,data,mpv,**kwargs):
|
||||
|
||||
class AnimdlStreamDialog(
|
||||
ThemableBehavior,
|
||||
StencilBehavior,
|
||||
CommonElevationBehavior,
|
||||
BackgroundColorBehavior,
|
||||
ModalView,
|
||||
):
|
||||
"""The anime streaming dialog"""
|
||||
|
||||
def __init__(self, data, mpv, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.data = data
|
||||
self.mpv=mpv
|
||||
if title:=data["title"].get("romaji"):
|
||||
self.mpv = mpv
|
||||
if title := data["title"].get("romaji"):
|
||||
self.ids.title_field.text = title
|
||||
elif title:=data["title"].get("english"):
|
||||
elif title := data["title"].get("english"):
|
||||
self.ids.title_field.text = title
|
||||
|
||||
self.ids.quality_field.text = "best"
|
||||
def stream_anime(self,app):
|
||||
|
||||
def _stream_anime(self, app):
|
||||
if self.mpv:
|
||||
streaming_cmds = {}
|
||||
title = self.ids.title_field.text
|
||||
@@ -27,7 +42,7 @@ class AnimdlStreamDialog(ThemableBehavior,StencilBehavior,CommonElevationBehavio
|
||||
quality = self.ids.quality_field.text
|
||||
if quality:
|
||||
streaming_cmds["quality"] = quality
|
||||
else:
|
||||
else:
|
||||
streaming_cmds["quality"] = "best"
|
||||
|
||||
app.watch_on_animdl(streaming_cmds)
|
||||
@@ -38,14 +53,17 @@ class AnimdlStreamDialog(ThemableBehavior,StencilBehavior,CommonElevationBehavio
|
||||
|
||||
episodes_range = self.ids.range_field.text
|
||||
if episodes_range:
|
||||
cmds = [*cmds,"-r",episodes_range]
|
||||
cmds = [*cmds, "-r", episodes_range]
|
||||
|
||||
latest = self.ids.latest_field.text
|
||||
if latest:
|
||||
cmds = [*cmds,"-s",latest]
|
||||
cmds = [*cmds, "-s", latest]
|
||||
|
||||
quality = self.ids.quality_field.text
|
||||
if quality:
|
||||
cmds = [*cmds,"-q",quality]
|
||||
cmds = [*cmds, "-q", quality]
|
||||
|
||||
app.watch_on_animdl(custom_options = cmds)
|
||||
app.watch_on_animdl(custom_options=cmds)
|
||||
|
||||
def stream_anime(self, app):
|
||||
Clock.schedule_once(lambda _: self._stream_anime(app))
|
||||
|
||||
@@ -1,46 +1,56 @@
|
||||
from kivy.properties import ObjectProperty,ListProperty
|
||||
from kivy.clock import Clock
|
||||
from kivy.properties import ObjectProperty, ListProperty
|
||||
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
|
||||
class AnimeCharacter(MDBoxLayout):
|
||||
voice_actors = ObjectProperty({
|
||||
"name":"",
|
||||
"image":""
|
||||
})
|
||||
character = ObjectProperty({
|
||||
"name":"",
|
||||
"gender":"",
|
||||
"dateOfBirth":"",
|
||||
"image":"",
|
||||
"age":"",
|
||||
"description":""
|
||||
})
|
||||
"""an Anime character data"""
|
||||
|
||||
voice_actors = ObjectProperty({"name": "", "image": ""})
|
||||
character = ObjectProperty(
|
||||
{
|
||||
"name": "",
|
||||
"gender": "",
|
||||
"dateOfBirth": "",
|
||||
"image": "",
|
||||
"age": "",
|
||||
"description": "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class AnimeCharacters(MDBoxLayout):
|
||||
"""The anime characters card"""
|
||||
|
||||
container = ObjectProperty()
|
||||
characters = ListProperty()
|
||||
def on_characters(self,instance,characters):
|
||||
format_date = lambda date_: f"{date_['day']}/{date_['month']}/{date_['year']}" if date_ else ""
|
||||
|
||||
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)
|
||||
for character_ in characters: # character (character,actor)
|
||||
character = character_[0]
|
||||
actors = character_[1]
|
||||
|
||||
anime_character = AnimeCharacter()
|
||||
anime_character.character = {
|
||||
"name":character["name"]["full"],
|
||||
"gender":character["gender"],
|
||||
"dateOfBirth":format_date(character["dateOfBirth"]),
|
||||
"image":character["image"]["medium"],
|
||||
"age":character["age"],
|
||||
"description":character["description"]
|
||||
"name": character["name"]["full"],
|
||||
"gender": character["gender"],
|
||||
"dateOfBirth": format_date(character["dateOfBirth"]),
|
||||
"image": character["image"]["medium"],
|
||||
"age": character["age"],
|
||||
"description": character["description"],
|
||||
}
|
||||
anime_character.voice_actors = {
|
||||
"name":", ".join([actor["name"]["full"] for actor in actors])
|
||||
"name": ", ".join([actor["name"]["full"] for actor in actors])
|
||||
}
|
||||
|
||||
# anime_character.voice_actor =
|
||||
self.container.add_widget(anime_character)
|
||||
|
||||
def on_characters(self, *args):
|
||||
Clock.schedule_once(lambda _: self.update_characters_card(*args))
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
padding:"10dp"
|
||||
spacing:"10dp"
|
||||
pos_hint: {'center_x': 0.5}
|
||||
# StackLayout:
|
||||
MDButton:
|
||||
on_press:
|
||||
root.screen.add_to_user_anime_list()
|
||||
|
||||
@@ -4,4 +4,6 @@ from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
|
||||
class Controls(MDBoxLayout):
|
||||
"""The diferent controls available"""
|
||||
|
||||
screen = ObjectProperty()
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
# <DescriptionHeader>
|
||||
# adaptive_height:True
|
||||
# md_bg_color:self.theme_cls.secondaryContainerColor
|
||||
# MDLabel:
|
||||
# text:root.text
|
||||
# adaptive_height:True
|
||||
# max_lines:0
|
||||
# shorten:False
|
||||
# bold:True
|
||||
# font_style: "Body"
|
||||
# role: "large"
|
||||
# padding:"10dp"
|
||||
|
||||
<DescriptionContainer@MDBoxLayout>:
|
||||
adaptive_height:True
|
||||
md_bg_color:self.theme_cls.surfaceContainerLowColor
|
||||
|
||||
@@ -4,5 +4,6 @@ from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
|
||||
class AnimeDescription(MDBoxLayout):
|
||||
description = StringProperty()
|
||||
"""The anime description"""
|
||||
|
||||
description = StringProperty()
|
||||
|
||||
@@ -1,26 +1,41 @@
|
||||
from kivy.uix.modalview import ModalView
|
||||
from kivymd.uix.behaviors import StencilBehavior,CommonElevationBehavior,BackgroundColorBehavior
|
||||
|
||||
from kivymd.uix.behaviors import (
|
||||
StencilBehavior,
|
||||
CommonElevationBehavior,
|
||||
BackgroundColorBehavior,
|
||||
)
|
||||
from kivymd.theming import ThemableBehavior
|
||||
|
||||
|
||||
# from main import AniXStreamApp
|
||||
class DownloadAnimeDialog(ThemableBehavior,StencilBehavior,CommonElevationBehavior,BackgroundColorBehavior,ModalView):
|
||||
def __init__(self,data,**kwargs):
|
||||
super(DownloadAnimeDialog,self).__init__(**kwargs)
|
||||
class DownloadAnimeDialog(
|
||||
ThemableBehavior,
|
||||
StencilBehavior,
|
||||
CommonElevationBehavior,
|
||||
BackgroundColorBehavior,
|
||||
ModalView,
|
||||
):
|
||||
"""The download anime dialog"""
|
||||
|
||||
def __init__(self, data, **kwargs):
|
||||
super(DownloadAnimeDialog, self).__init__(**kwargs)
|
||||
self.data = data
|
||||
self.anime_id = self.data["id"]
|
||||
if title:=data["title"].get("romaji"):
|
||||
if title := data["title"].get("romaji"):
|
||||
self.ids.title_field.text = title
|
||||
elif title:=data["title"].get("english"):
|
||||
elif title := data["title"].get("english"):
|
||||
self.ids.title_field.text = title
|
||||
self.ids.quality_field.text = "best"
|
||||
def download_anime(self,app):
|
||||
|
||||
def download_anime(self, app):
|
||||
default_cmds = {}
|
||||
title=self.ids.title_field.text
|
||||
title = self.ids.title_field.text
|
||||
default_cmds["title"] = title
|
||||
if episodes_range:=self.ids.range_field.text:
|
||||
if episodes_range := self.ids.range_field.text:
|
||||
default_cmds["episodes_range"] = episodes_range
|
||||
|
||||
if quality:=self.ids.range_field.text:
|
||||
if quality := self.ids.range_field.text:
|
||||
default_cmds["quality"] = quality
|
||||
|
||||
# print(title,episodes_range,latest,quality)
|
||||
app.download_anime(self.anime_id,default_cmds)
|
||||
app.download_anime(self.anime_id, default_cmds)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<AnimeHeader>:
|
||||
adaptive_height:True
|
||||
orientation: 'vertical'
|
||||
# padding:"10dp"
|
||||
MDBoxLayout:
|
||||
adaptive_height:True
|
||||
md_bg_color:self.theme_cls.secondaryContainerColor
|
||||
|
||||
@@ -6,4 +6,3 @@ from kivymd.uix.boxlayout import MDBoxLayout
|
||||
class AnimeHeader(MDBoxLayout):
|
||||
titles = StringProperty()
|
||||
banner_image = StringProperty()
|
||||
|
||||
|
||||
@@ -6,9 +6,8 @@ from kivymd.uix.boxlayout import MDBoxLayout
|
||||
class RankingsBar(MDBoxLayout):
|
||||
rankings = DictProperty(
|
||||
{
|
||||
"Popularity":0,
|
||||
"Favourites":0,
|
||||
"AverageScore":0,
|
||||
"Popularity": 0,
|
||||
"Favourites": 0,
|
||||
"AverageScore": 0,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
from kivy.properties import ObjectProperty,ListProperty
|
||||
from kivy.properties import ObjectProperty, ListProperty
|
||||
from kivy.clock import Clock
|
||||
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
|
||||
class AnimeReview(MDBoxLayout):
|
||||
review = ObjectProperty({
|
||||
"username":"",
|
||||
"avatar":"",
|
||||
"summary":""
|
||||
})
|
||||
review = ObjectProperty({"username": "", "avatar": "", "summary": ""})
|
||||
|
||||
|
||||
class AnimeReviews(MDBoxLayout):
|
||||
"""anime reviews"""
|
||||
|
||||
reviews = ListProperty()
|
||||
container = ObjectProperty()
|
||||
def on_reviews(self,instance,reviews):
|
||||
|
||||
def on_reviews(self, *args):
|
||||
Clock.schedule_once(lambda _: self.update_reviews_card(*args))
|
||||
|
||||
def update_reviews_card(self, instance, reviews):
|
||||
self.container.clear_widgets()
|
||||
for review in reviews:
|
||||
review_ = AnimeReview()
|
||||
review_.review = {
|
||||
"username":review["user"]["name"],
|
||||
"avatar":review["user"]["avatar"]["medium"],
|
||||
"summary":review["summary"]
|
||||
"username": review["user"]["name"],
|
||||
"avatar": review["user"]["avatar"]["medium"],
|
||||
"summary": review["summary"],
|
||||
}
|
||||
self.container.add_widget(review_)
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
spacing:"10dp"
|
||||
orientation: 'vertical'
|
||||
pos_hint: {'center_x': 0.5}
|
||||
|
||||
|
||||
<SideBarLabel>:
|
||||
adaptive_height:True
|
||||
max_lines:0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from kivy.properties import ObjectProperty,StringProperty,DictProperty,ListProperty
|
||||
from kivy.properties import ObjectProperty, StringProperty, DictProperty, ListProperty
|
||||
from kivy.utils import get_hex_from_color
|
||||
from kivy.factory import Factory
|
||||
|
||||
@@ -10,34 +10,42 @@ class HeaderLabel(MDBoxLayout):
|
||||
text = StringProperty()
|
||||
halign = StringProperty("center")
|
||||
|
||||
|
||||
Factory.register("HeaderLabel", HeaderLabel)
|
||||
|
||||
|
||||
class SideBarLabel(MDLabel):
|
||||
pass
|
||||
|
||||
|
||||
# TODO:Switch to using the kivy_markup_module
|
||||
class AnimeSideBar(MDBoxLayout):
|
||||
screen = ObjectProperty()
|
||||
image = StringProperty()
|
||||
alternative_titles = DictProperty({
|
||||
"synonyms":"",
|
||||
"english":"",
|
||||
"japanese":"",
|
||||
})
|
||||
information = DictProperty({
|
||||
"episodes":"",
|
||||
"status":"",
|
||||
"aired":"",
|
||||
"nextAiringEpisode":"",
|
||||
"premiered":"",
|
||||
"broadcast":"",
|
||||
"countryOfOrigin":"",
|
||||
"hashtag":"",
|
||||
"studios":"", # { "name": "Sunrise", "isAnimationStudio": true }
|
||||
"source":"",
|
||||
"genres":"",
|
||||
"duration":"",
|
||||
"producers":"",
|
||||
})
|
||||
alternative_titles = DictProperty(
|
||||
{
|
||||
"synonyms": "",
|
||||
"english": "",
|
||||
"japanese": "",
|
||||
}
|
||||
)
|
||||
information = DictProperty(
|
||||
{
|
||||
"episodes": "",
|
||||
"status": "",
|
||||
"aired": "",
|
||||
"nextAiringEpisode": "",
|
||||
"premiered": "",
|
||||
"broadcast": "",
|
||||
"countryOfOrigin": "",
|
||||
"hashtag": "",
|
||||
"studios": "", # { "name": "Sunrise", "isAnimationStudio": true }
|
||||
"source": "",
|
||||
"genres": "",
|
||||
"duration": "",
|
||||
"producers": "",
|
||||
}
|
||||
)
|
||||
statistics = ListProperty()
|
||||
statistics_container = ObjectProperty()
|
||||
external_links = ListProperty()
|
||||
@@ -45,7 +53,7 @@ class AnimeSideBar(MDBoxLayout):
|
||||
tags = ListProperty()
|
||||
tags_container = ObjectProperty()
|
||||
|
||||
def on_statistics(self,instance,value):
|
||||
def on_statistics(self, instance, value):
|
||||
self.statistics_container.clear_widgets()
|
||||
header = HeaderLabel()
|
||||
header.text = "Rankings"
|
||||
@@ -56,10 +64,11 @@ class AnimeSideBar(MDBoxLayout):
|
||||
label.text = "[color={}]{}:[/color] {}".format(
|
||||
get_hex_from_color(label.theme_cls.primaryColor),
|
||||
stat[0].capitalize(),
|
||||
f"{stat[1]}")
|
||||
f"{stat[1]}",
|
||||
)
|
||||
self.statistics_container.add_widget(label)
|
||||
|
||||
def on_tags(self,instance,value):
|
||||
def on_tags(self, instance, value):
|
||||
self.tags_container.clear_widgets()
|
||||
header = HeaderLabel()
|
||||
header.text = "Tags"
|
||||
@@ -69,11 +78,11 @@ class AnimeSideBar(MDBoxLayout):
|
||||
label.text = "[color={}]{}:[/color] {}".format(
|
||||
get_hex_from_color(label.theme_cls.primaryColor),
|
||||
tag[0].capitalize(),
|
||||
f"{tag[1]} %")
|
||||
f"{tag[1]} %",
|
||||
)
|
||||
self.tags_container.add_widget(label)
|
||||
|
||||
|
||||
def on_external_links(self,instance,value):
|
||||
def on_external_links(self, instance, value):
|
||||
self.external_links_container.clear_widgets()
|
||||
header = HeaderLabel()
|
||||
header.text = "External Links"
|
||||
@@ -84,5 +93,6 @@ class AnimeSideBar(MDBoxLayout):
|
||||
label.text = "[color={}]{}:[/color] {}".format(
|
||||
get_hex_from_color(label.theme_cls.primaryColor),
|
||||
site[0].capitalize(),
|
||||
site[1])
|
||||
site[1],
|
||||
)
|
||||
self.external_links_container.add_widget(label)
|
||||
|
||||
@@ -3,6 +3,7 @@ from View.base_screen import BaseScreenView
|
||||
|
||||
|
||||
class CrashLogScreenView(BaseScreenView):
|
||||
"""The crash log screen"""
|
||||
main_container = ObjectProperty()
|
||||
def model_is_changed(self) -> None:
|
||||
"""
|
||||
|
||||
@@ -25,4 +25,4 @@
|
||||
theme_text_color:"Secondary"
|
||||
text:color_text(root.episodes_to_download,root.theme_cls.secondaryColor)
|
||||
MDIcon:
|
||||
icon:"check-bold"
|
||||
icon:"download"
|
||||
|
||||
@@ -4,29 +4,29 @@ from kivy.logger import Logger
|
||||
from kivy.utils import format_bytes_to_human
|
||||
|
||||
from View.base_screen import BaseScreenView
|
||||
from .components.task_card import TaskCard
|
||||
from .components.task_card import TaskCard
|
||||
|
||||
|
||||
class DownloadsScreenView(BaseScreenView):
|
||||
main_container = ObjectProperty()
|
||||
progress_bar = ObjectProperty()
|
||||
download_progress_label = ObjectProperty()
|
||||
|
||||
def on_new_download_task(self,anime_title:str,episodes:str|None):
|
||||
def on_new_download_task(self, anime_title: str, episodes: str | None):
|
||||
if not episodes:
|
||||
episodes = "All"
|
||||
self.main_container.add_widget(TaskCard(anime_title,episodes))
|
||||
Clock.schedule_once(
|
||||
lambda _: self.main_container.add_widget(TaskCard(anime_title, episodes))
|
||||
)
|
||||
|
||||
def on_episode_download_progress(self,current_bytes_downloaded,total_bytes,episode_info):
|
||||
percentage_completion = round((current_bytes_downloaded/total_bytes)*100)
|
||||
def on_episode_download_progress(
|
||||
self, current_bytes_downloaded, total_bytes, episode_info
|
||||
):
|
||||
percentage_completion = round((current_bytes_downloaded / total_bytes) * 100)
|
||||
progress_text = f"Downloading: {episode_info['anime_title']} - {episode_info['episode']} ({format_bytes_to_human(current_bytes_downloaded)}/{format_bytes_to_human(total_bytes)})"
|
||||
if (percentage_completion%5)==0:
|
||||
self.progress_bar.value= max(min(percentage_completion,100),0)
|
||||
if (percentage_completion % 5) == 0:
|
||||
self.progress_bar.value = max(min(percentage_completion, 100), 0)
|
||||
self.download_progress_label.text = progress_text
|
||||
Logger.info(f"Downloader: {progress_text}")
|
||||
|
||||
|
||||
# def on_enter(self):
|
||||
# Clock.schedule_once(lambda _:self.controller.requested_update_my_list_screen())
|
||||
|
||||
def update_layout(self,widget):
|
||||
def update_layout(self, widget):
|
||||
self.user_anime_list_container.add_widget(widget)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#:import get_color_from_hex kivy.utils.get_color_from_hex
|
||||
#:import StringProperty kivy.properties.StringProperty
|
||||
|
||||
|
||||
|
||||
<HelpCard@MDBoxLayout>
|
||||
spacing:"10dp"
|
||||
orientation:"vertical"
|
||||
|
||||
@@ -4,11 +4,13 @@ from View.base_screen import BaseScreenView
|
||||
from Utility.kivy_markup_helper import bolden, color_text, underline
|
||||
from Utility.data import themes_available
|
||||
|
||||
|
||||
class HelpScreenView(BaseScreenView):
|
||||
main_container = ObjectProperty()
|
||||
animdl_help = StringProperty()
|
||||
installing_animdl_help = StringProperty()
|
||||
available_themes = StringProperty()
|
||||
|
||||
def __init__(self, **kw):
|
||||
super(HelpScreenView, self).__init__(**kw)
|
||||
self.animdl_help = f"""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from kivy.properties import ObjectProperty
|
||||
|
||||
from View.base_screen import BaseScreenView
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
from kivy.properties import ObjectProperty,StringProperty,DictProperty
|
||||
from kivy.properties import ObjectProperty, StringProperty, DictProperty
|
||||
from kivy.clock import Clock
|
||||
|
||||
from View.base_screen import BaseScreenView
|
||||
|
||||
|
||||
class MyListScreenView(BaseScreenView):
|
||||
user_anime_list_container = ObjectProperty()
|
||||
|
||||
def model_is_changed(self) -> None:
|
||||
"""
|
||||
Called whenever any change has occurred in the data model.
|
||||
The view in this method tracks these changes and updates the UI
|
||||
according to these changes.
|
||||
"""
|
||||
|
||||
def on_enter(self):
|
||||
Clock.schedule_once(lambda _:self.controller.requested_update_my_list_screen())
|
||||
|
||||
def update_layout(self,widget):
|
||||
self.user_anime_list_container.add_widget(widget)
|
||||
|
||||
def on_enter(self):
|
||||
Clock.schedule_once(lambda _: self.controller.requested_update_my_list_screen())
|
||||
|
||||
def update_layout(self, widget):
|
||||
self.user_anime_list_container.add_widget(widget)
|
||||
|
||||
@@ -58,7 +58,6 @@ class Filters(MDBoxLayout):
|
||||
"NOT_YET_RELEASED",
|
||||
"CANCELLED",
|
||||
"HIATUS",
|
||||
|
||||
]
|
||||
case _:
|
||||
items = []
|
||||
|
||||
@@ -2,7 +2,7 @@ from kivy.properties import ObjectProperty, StringProperty
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.screen import MDScreen
|
||||
from kivymd.uix.navigationrail import MDNavigationRail
|
||||
from kivymd.uix.navigationrail import MDNavigationRail, MDNavigationRailItem
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.button import MDIconButton
|
||||
from kivymd.uix.tooltip import MDTooltip
|
||||
@@ -26,6 +26,11 @@ class TooltipMDIconButton(Tooltip, MDIconButton):
|
||||
tooltip_text = StringProperty()
|
||||
|
||||
|
||||
class CommonNavigationRailItem(MDNavigationRailItem):
|
||||
icon = StringProperty()
|
||||
text = StringProperty()
|
||||
|
||||
|
||||
class BaseScreenView(MDScreen, Observer):
|
||||
"""
|
||||
A base class that implements a visual representation of the model data.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# <MDLabel>:
|
||||
# allow_copy:True
|
||||
# allow_selection:True
|
||||
<MDLabel>:
|
||||
allow_copy:True
|
||||
allow_selection:True
|
||||
@@ -4,18 +4,29 @@ from kivy.animation import Animation
|
||||
from kivy.uix.modalview import ModalView
|
||||
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.behaviors import BackgroundColorBehavior,StencilBehavior,CommonElevationBehavior,HoverBehavior
|
||||
from kivymd.uix.behaviors import (
|
||||
BackgroundColorBehavior,
|
||||
StencilBehavior,
|
||||
CommonElevationBehavior,
|
||||
HoverBehavior,
|
||||
)
|
||||
|
||||
|
||||
class MediaPopup(ThemableBehavior,HoverBehavior,StencilBehavior,CommonElevationBehavior,BackgroundColorBehavior,ModalView):
|
||||
class MediaPopup(
|
||||
ThemableBehavior,
|
||||
HoverBehavior,
|
||||
StencilBehavior,
|
||||
CommonElevationBehavior,
|
||||
BackgroundColorBehavior,
|
||||
ModalView,
|
||||
):
|
||||
caller = ObjectProperty()
|
||||
player = ObjectProperty()
|
||||
|
||||
def __init__(self, caller,*args,**kwarg):
|
||||
def __init__(self, caller, *args, **kwarg):
|
||||
self.caller = caller
|
||||
super(MediaPopup,self).__init__(*args,**kwarg)
|
||||
super(MediaPopup, self).__init__(*args, **kwarg)
|
||||
|
||||
|
||||
def open(self, *_args, **kwargs):
|
||||
"""Display the modal in the Window.
|
||||
|
||||
@@ -26,33 +37,32 @@ class MediaPopup(ThemableBehavior,HoverBehavior,StencilBehavior,CommonElevationB
|
||||
|
||||
"""
|
||||
from kivy.core.window import Window
|
||||
|
||||
if self._is_open:
|
||||
return
|
||||
self._window = Window
|
||||
self._is_open = True
|
||||
self.dispatch('on_pre_open')
|
||||
self.dispatch("on_pre_open")
|
||||
Window.add_widget(self)
|
||||
Window.bind(
|
||||
on_resize=self._align_center,
|
||||
on_keyboard=self._handle_keyboard)
|
||||
Window.bind(on_resize=self._align_center, on_keyboard=self._handle_keyboard)
|
||||
self.center = self.caller.to_window(*self.caller.center)
|
||||
self.fbind('center', self._align_center)
|
||||
self.fbind('size', self._align_center)
|
||||
if kwargs.get('animation', True):
|
||||
ani = Animation(_anim_alpha=1., d=self._anim_duration)
|
||||
ani.bind(on_complete=lambda *_args: self.dispatch('on_open'))
|
||||
self.fbind("center", self._align_center)
|
||||
self.fbind("size", self._align_center)
|
||||
if kwargs.get("animation", True):
|
||||
ani = Animation(_anim_alpha=1.0, d=self._anim_duration)
|
||||
ani.bind(on_complete=lambda *_args: self.dispatch("on_open"))
|
||||
ani.start(self)
|
||||
else:
|
||||
self._anim_alpha = 1.
|
||||
self.dispatch('on_open')
|
||||
self._anim_alpha = 1.0
|
||||
self.dispatch("on_open")
|
||||
|
||||
def _align_center(self, *_args):
|
||||
if self._is_open:
|
||||
self.center = self.caller.to_window(*self.caller.center)
|
||||
|
||||
|
||||
def on_leave(self,*args):
|
||||
def on_leave(self, *args):
|
||||
def _leave(dt):
|
||||
if not self.hovering:
|
||||
self.dismiss()
|
||||
Clock.schedule_once(_leave,2)
|
||||
|
||||
Clock.schedule_once(_leave, 2)
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
<CommonNavigationRailItem@MDNavigationRailItem>
|
||||
icon:""
|
||||
text:""
|
||||
|
||||
|
||||
<CommonNavigationRailItem>
|
||||
MDNavigationRailItemIcon:
|
||||
icon:root.icon
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
from subprocess import Popen, run, PIPE, CompletedProcess
|
||||
from typing import Callable
|
||||
|
||||
|
||||
from .extras import Logger
|
||||
from .animdl_data_helper import (
|
||||
filter_broken_streams,
|
||||
@@ -23,7 +21,7 @@ from .animdl_exceptions import (
|
||||
NoValidAnimeStreamsException,
|
||||
Python310NotFoundException,
|
||||
)
|
||||
from .animdl_types import AnimdlAnimeUrlAndTitle, AnimdlData
|
||||
from .animdl_types import AnimdlAnimeEpisode, AnimdlAnimeUrlAndTitle, AnimdlData
|
||||
|
||||
|
||||
broken_link_pattern = r"https://tools.fast4speed.rsvp/\w*"
|
||||
@@ -129,12 +127,25 @@ class AnimdlApi:
|
||||
)
|
||||
return most_likely_anime_url_and_title # ("title","anime url")
|
||||
else:
|
||||
raise AnimdlAnimeUrlNotFoundException
|
||||
raise AnimdlAnimeUrlNotFoundException(
|
||||
"The anime your searching for doesnt exist or animdl is broken or not in your system path"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def stream_anime_by_title_on_animdl(
|
||||
cls, title, episodes_range=None, quality: str = "best"
|
||||
cls, title: str, episodes_range: str | None = None, quality: str = "best"
|
||||
) -> Popen:
|
||||
"""Streams the anime title on animdl
|
||||
|
||||
Args:
|
||||
title (str): the anime title you want to stream
|
||||
episodes_range (str, optional): the episodes you want to stream; should be a valid animdl range. Defaults to None.
|
||||
quality (str, optional): the quality of the stream. Defaults to "best".
|
||||
|
||||
Returns:
|
||||
Popen: the stream child subprocess for mor control
|
||||
"""
|
||||
|
||||
anime = cls.get_anime_url_by_title(title)
|
||||
|
||||
base_cmds = ["stream", anime[1], "-q", quality]
|
||||
@@ -145,6 +156,17 @@ class AnimdlApi:
|
||||
def stream_anime_with_mpv(
|
||||
cls, title: str, episodes_range: str | None = None, quality: str = "best"
|
||||
):
|
||||
"""Stream an anime directly with mpv without having to interact with animdl cli
|
||||
|
||||
Args:
|
||||
title (str): the anime title you want to stream
|
||||
episodes_range (str | None, optional): a valid animdl episodes range you want ito watch. Defaults to None.
|
||||
quality (str, optional): the quality of the stream. Defaults to "best".
|
||||
|
||||
Yields:
|
||||
Popen: the child subprocess you currently are watching
|
||||
"""
|
||||
|
||||
anime_data = cls.get_all_stream_urls_by_anime_title(title, episodes_range)
|
||||
stream = []
|
||||
for episode in anime_data.episodes:
|
||||
@@ -186,8 +208,18 @@ class AnimdlApi:
|
||||
|
||||
@classmethod
|
||||
def get_all_anime_stream_urls_by_anime_url(
|
||||
cls, anime_url: str, episodes_range=None
|
||||
):
|
||||
cls, anime_url: str, episodes_range: str | None = None
|
||||
) -> list[AnimdlAnimeEpisode]:
|
||||
"""gets all the streams for the animdl url
|
||||
|
||||
Args:
|
||||
anime_url (str): an animdl url used in scraping
|
||||
episodes_range (str | None, optional): a valid animdl episodes range. Defaults to None.
|
||||
|
||||
Returns:
|
||||
list[AnimdlAnimeEpisode]: A list of anime episodes gotten from animdl
|
||||
"""
|
||||
|
||||
cmd = (
|
||||
["grab", anime_url, "-r", episodes_range]
|
||||
if episodes_range
|
||||
@@ -207,8 +239,9 @@ class AnimdlApi:
|
||||
episodes_range (str, optional): an animdl episodes range. Defaults to None.
|
||||
|
||||
Returns:
|
||||
_type_: _description_
|
||||
AnimdlData: The parsed data from animdl grab
|
||||
"""
|
||||
|
||||
possible_anime = cls.get_anime_url_by_title(title)
|
||||
return AnimdlData(
|
||||
possible_anime.anime_title,
|
||||
@@ -235,6 +268,23 @@ class AnimdlApi:
|
||||
episodes_range: str | None = None,
|
||||
quality: str = "best",
|
||||
) -> tuple[list[int], list[int]]:
|
||||
"""Downloads anime either adaptive, progressive, or .m3u streams and uses mpv to achieve this
|
||||
|
||||
Args:
|
||||
_anime_title (str): the anime title you want to download
|
||||
on_episode_download_progress (Callable): the callback when a chunk of an episode is downloaded
|
||||
on_episode_download_complete (Callable): the callback when an episode has been successfully downloaded
|
||||
on_complete (Callable): callback when the downloading process is complete
|
||||
output_path (str): the directory | folder to download the anime
|
||||
episodes_range (str | None, optional): a valid animdl episode range. Defaults to None.
|
||||
quality (str, optional): the anime quality. Defaults to "best".
|
||||
|
||||
Raises:
|
||||
NoValidAnimeStreamsException: raised when no valid streams were found for a particular episode
|
||||
|
||||
Returns:
|
||||
tuple[list[int], list[int]]: a tuple containing successful, and failed downloads list
|
||||
"""
|
||||
|
||||
anime_streams_data = cls.get_all_stream_urls_by_anime_title(
|
||||
_anime_title, episodes_range
|
||||
@@ -260,6 +310,11 @@ class AnimdlApi:
|
||||
streams = filter_broken_streams(episode["streams"])
|
||||
|
||||
# raises an exception if no streams for current episodes
|
||||
if not streams:
|
||||
raise NoValidAnimeStreamsException(
|
||||
f"No valid streams were found for episode {episode_number}"
|
||||
)
|
||||
|
||||
episode_stream = filter_streams_by_quality(streams, quality)
|
||||
|
||||
# determine episode_title
|
||||
@@ -340,6 +395,17 @@ class AnimdlApi:
|
||||
|
||||
@classmethod
|
||||
def download_with_mpv(cls, url: str, output_path: str, on_progress: Callable):
|
||||
"""The method used to download a remote resource with mpv
|
||||
|
||||
Args:
|
||||
url (str): the url of the remote resource to download
|
||||
output_path (str): the location to download the resource to
|
||||
on_progress (Callable): the callback when a chunk of the resource is downloaded
|
||||
|
||||
Returns:
|
||||
subprocess return code: the return code of the mpv subprocess
|
||||
"""
|
||||
|
||||
mpv_child_process = run_mpv_command(url, f"--stream-dump={output_path}")
|
||||
progress_regex = re.compile(r"\d+/\d+") # eg Dumping 2044776/125359745
|
||||
|
||||
@@ -361,6 +427,18 @@ class AnimdlApi:
|
||||
episode_info: dict[str, str],
|
||||
on_progress: Callable,
|
||||
):
|
||||
"""the progressive downloader of mpv
|
||||
|
||||
Args:
|
||||
video_url (str): a video url
|
||||
output_path (str): download location
|
||||
episode_info (dict[str, str]): the details of the episode we downloading
|
||||
on_progress (Callable): the callback when a chunk is downloaded
|
||||
|
||||
Raises:
|
||||
Exception: exception raised when anything goes wrong
|
||||
"""
|
||||
|
||||
episode = (
|
||||
path_parser(episode_info["anime_title"])
|
||||
+ " - "
|
||||
@@ -385,6 +463,20 @@ class AnimdlApi:
|
||||
on_progress: Callable,
|
||||
episode_info: dict[str, str],
|
||||
):
|
||||
"""the adaptive downloader
|
||||
|
||||
Args:
|
||||
video_url (str): url of video you want ot download
|
||||
audio_url (str): url of audio file you want ot download
|
||||
sub_url (str): url of sub file you want ot download
|
||||
output_path (str): download location
|
||||
on_progress (Callable): the callback when a chunk is downloaded
|
||||
episode_info (dict[str, str]): episode details
|
||||
|
||||
Raises:
|
||||
Exception: incase anything goes wrong
|
||||
"""
|
||||
|
||||
on_progress_ = lambda current_bytes, total_bytes: on_progress(
|
||||
current_bytes, total_bytes, episode_info
|
||||
)
|
||||
@@ -421,6 +513,19 @@ class AnimdlApi:
|
||||
on_progress: Callable,
|
||||
episode_info: dict[str, str],
|
||||
):
|
||||
"""only downloads video and subs
|
||||
|
||||
Args:
|
||||
video_url (str): url of video you want ot download
|
||||
sub_url (str): url of sub you want ot download
|
||||
output_path (str): the download location
|
||||
on_progress (Callable): the callback for when a chunk is downloaded
|
||||
episode_info (dict[str, str]): episode details
|
||||
|
||||
Raises:
|
||||
Exception: when anything goes wrong
|
||||
"""
|
||||
|
||||
on_progress_ = lambda current_bytes, total_bytes: on_progress(
|
||||
current_bytes, total_bytes, episode_info
|
||||
)
|
||||
@@ -441,21 +546,3 @@ class AnimdlApi:
|
||||
|
||||
if is_video_failure:
|
||||
raise Exception
|
||||
|
||||
|
||||
# TODO: ADD RUN_MPV_COMMAND = RAISES MPV NOT FOR ND EXCEPTION
|
||||
# TODO: ADD STREAM WITH MPV
|
||||
if __name__ == "__main__":
|
||||
title = input("enter title: ")
|
||||
e_range = input("enter range: ")
|
||||
start = time.time()
|
||||
# t = AnimdlApi.download_anime_by_title(
|
||||
# title, lambda *u: print(u), lambda *u: print(u)
|
||||
# ,lambda *u:print(u),".",episodes_range=e_range)
|
||||
streamer = AnimdlApi.stream_anime_with_mpv(title, e_range, quality="720")
|
||||
# with open("test.json","w") as file:
|
||||
# print(json.dump(t,file))
|
||||
for stream in streamer:
|
||||
print(stream.communicate())
|
||||
delta = time.time() - start
|
||||
print(f"Took: {delta} secs")
|
||||
|
||||
@@ -4,7 +4,11 @@ import json
|
||||
from fuzzywuzzy import fuzz
|
||||
|
||||
from .extras import Logger
|
||||
from .animdl_types import AnimdlAnimeUrlAndTitle,AnimdlData,AnimdlAnimeEpisode,AnimdlEpisodeStream
|
||||
from .animdl_types import (
|
||||
AnimdlAnimeUrlAndTitle,
|
||||
AnimdlAnimeEpisode,
|
||||
AnimdlEpisodeStream,
|
||||
)
|
||||
|
||||
|
||||
# Currently this links don't work so we filter it out
|
||||
@@ -70,6 +74,15 @@ def anime_title_percentage_match(
|
||||
def filter_broken_streams(
|
||||
streams: list[AnimdlEpisodeStream],
|
||||
) -> list[AnimdlEpisodeStream]:
|
||||
"""filters the streams that the project has evaluated doesnt work
|
||||
|
||||
Args:
|
||||
streams (list[AnimdlEpisodeStream]): the streams to filter
|
||||
|
||||
Returns:
|
||||
list[AnimdlEpisodeStream]: the valid streams
|
||||
"""
|
||||
|
||||
stream_filter = lambda stream: (
|
||||
True if not re.match(broken_link_pattern, stream["stream_url"]) else False
|
||||
)
|
||||
@@ -77,9 +90,19 @@ def filter_broken_streams(
|
||||
|
||||
|
||||
def filter_streams_by_quality(
|
||||
anime_episode_streams: list[AnimdlEpisodeStream], quality: str|int, strict=False
|
||||
anime_episode_streams: list[AnimdlEpisodeStream], quality: str | int, strict=False
|
||||
) -> AnimdlEpisodeStream:
|
||||
# filtered_streams = []
|
||||
"""filters streams by quality
|
||||
|
||||
Args:
|
||||
anime_episode_streams (list[AnimdlEpisodeStream]): the streams to filter
|
||||
quality (str | int): the quality you want to get
|
||||
strict (bool, optional): whether to always return an episode if quality isn,t found. Defaults to False.
|
||||
|
||||
Returns:
|
||||
AnimdlEpisodeStream: the stream of specified quality
|
||||
"""
|
||||
|
||||
# get the appropriate stream or default to best
|
||||
get_quality_func = lambda stream_: (
|
||||
stream_.get("quality") if stream_.get("quality") else 0
|
||||
@@ -104,8 +127,16 @@ def filter_streams_by_quality(
|
||||
# return AnimdlEpisodeStream({})
|
||||
|
||||
|
||||
# TODO: add typing to return dict
|
||||
def parse_stream_urls_data(raw_stream_urls_data: str) -> list[AnimdlAnimeEpisode]:
|
||||
"""parses the streams data gotten from animdl grab
|
||||
|
||||
Args:
|
||||
raw_stream_urls_data (str): the animdl grab data to parse
|
||||
|
||||
Returns:
|
||||
list[AnimdlAnimeEpisode]: the parsed streams for all episode
|
||||
"""
|
||||
|
||||
try:
|
||||
return [
|
||||
AnimdlAnimeEpisode(json.loads(episode.strip()))
|
||||
@@ -123,8 +154,9 @@ def search_output_parser(raw_data: str) -> list[AnimdlAnimeUrlAndTitle]:
|
||||
raw_data (str): valid animdl data
|
||||
|
||||
Returns:
|
||||
dict: parsed animdl data containing an anime title
|
||||
AnimdlAnimeUrlAndTitle: parsed animdl data containing an animdl anime url and anime title
|
||||
"""
|
||||
|
||||
# get each line of dat and ignore those that contain unwanted data
|
||||
data = raw_data.split("\n")[3:]
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
["jujutsu kaisen", [{"episode": 1, "streams": [{"stream_url": "https://tools.fast4speed.rsvp//media6/videos/CRZx43dgcfpWecx7W/sub/1"}]}, {"episode": 2, "streams": [{"stream_url": "https://tools.fast4speed.rsvp//media6/videos/CRZx43dgcfpWecx7W/sub/2"}]}, {"episode": 3, "streams": [{"stream_url": "https://tools.fast4speed.rsvp//media6/videos/CRZx43dgcfpWecx7W/sub/3"}]}]]
|
||||
52
app/main.py
52
app/main.py
@@ -42,7 +42,6 @@ if not (user_data_helper.yt_cache.exists("yt_stream_links")):
|
||||
# TODO: Confirm data integrity from user_data and yt_cache
|
||||
|
||||
|
||||
# TODO: Arrange the app methods
|
||||
class AniXStreamApp(MDApp):
|
||||
queue = Queue()
|
||||
downloads_queue = Queue()
|
||||
@@ -51,13 +50,19 @@ class AniXStreamApp(MDApp):
|
||||
def worker(self, queue: Queue):
|
||||
while True:
|
||||
task = queue.get() # task should be a function
|
||||
task()
|
||||
try:
|
||||
task()
|
||||
except Exception as e:
|
||||
show_notification("An error occured while streaming",f"{e}")
|
||||
self.queue.task_done()
|
||||
|
||||
def downloads_worker(self, queue: Queue):
|
||||
while True:
|
||||
download_task = queue.get() # task should be a function
|
||||
download_task()
|
||||
try:
|
||||
download_task()
|
||||
except Exception as e:
|
||||
show_notification("An error occured while downloading",f"{e}")
|
||||
self.downloads_queue.task_done()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@@ -111,12 +116,20 @@ class AniXStreamApp(MDApp):
|
||||
self.manager_screens.add_widget(view)
|
||||
|
||||
def build_config(self, config):
|
||||
if vid_path := plyer.storagepath.get_videos_dir(): # type: ignore
|
||||
downloads_dir = os.path.join(vid_path, "anixstream")
|
||||
if not os.path.exists(downloads_dir):
|
||||
os.mkdir(downloads_dir)
|
||||
else:
|
||||
downloads_dir = os.path.join(".", "videos")
|
||||
if not os.path.exists(downloads_dir):
|
||||
os.mkdir(downloads_dir)
|
||||
config.setdefaults(
|
||||
"Preferences",
|
||||
{
|
||||
"theme_color": "Cyan",
|
||||
"theme_style": "Dark",
|
||||
"downloads_dir": plyer.storagepath.get_videos_dir() if plyer.storagepath.get_videos_dir() else ".", # type: ignore
|
||||
"downloads_dir": downloads_dir,
|
||||
"is_startup_anime_enable": False,
|
||||
},
|
||||
)
|
||||
@@ -204,6 +217,9 @@ class AniXStreamApp(MDApp):
|
||||
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,
|
||||
@@ -216,7 +232,9 @@ class AniXStreamApp(MDApp):
|
||||
download_task = lambda: AnimdlApi.download_anime_by_title(
|
||||
default_cmds["title"],
|
||||
on_progress,
|
||||
lambda *arg:print(arg),
|
||||
lambda anime_title, episode: show_notification(
|
||||
"Finished installing an episode", f"{anime_title}-{episode}"
|
||||
),
|
||||
self.download_anime_complete,
|
||||
output_path,
|
||||
) # ,default_cmds.get("quality")
|
||||
@@ -271,20 +289,20 @@ class AniXStreamApp(MDApp):
|
||||
)
|
||||
|
||||
def stream_anime_with_mpv(
|
||||
self, title, episodes_range: str | None = None,quality:str="best"
|
||||
self, title, episodes_range: str | None = None, quality: str = "best"
|
||||
):
|
||||
self.stop_streaming = False
|
||||
streams = AnimdlApi.stream_anime_with_mpv(title,episodes_range,quality)
|
||||
# TODO: End mpv child process properly
|
||||
streams = AnimdlApi.stream_anime_with_mpv(title, episodes_range, quality)
|
||||
# TODO: End mpv child process properly
|
||||
for stream in streams:
|
||||
self.animdl_streaming_subprocess= stream
|
||||
for line in self.animdl_streaming_subprocess.stderr: # type: ignore
|
||||
self.animdl_streaming_subprocess = stream
|
||||
for line in self.animdl_streaming_subprocess.stderr: # type: ignore
|
||||
if self.stop_streaming:
|
||||
if stream:
|
||||
stream.terminate()
|
||||
stream.kill()
|
||||
del stream
|
||||
return
|
||||
return
|
||||
|
||||
def watch_on_animdl(
|
||||
self,
|
||||
@@ -306,20 +324,24 @@ class AniXStreamApp(MDApp):
|
||||
self.animdl_streaming_subprocess.kill()
|
||||
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"]
|
||||
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']}")
|
||||
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
|
||||
)
|
||||
self.queue.put(stream_func)
|
||||
show_notification("Streamer","Started streaming")
|
||||
show_notification("Streamer", "Started streaming")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
AniXStreamApp().run()
|
||||
|
||||
@@ -4,4 +4,6 @@ ffpyplayer
|
||||
plyer
|
||||
https://github.com/kivymd/KivyMD/archive/master.zip
|
||||
fuzzywuzzy
|
||||
python-Levenshtein
|
||||
python-Levenshtein
|
||||
pyyaml
|
||||
animdl
|
||||
@@ -1 +1 @@
|
||||
{"user_anime_list": {"user_anime_list": [166531, 98437, 269, 104462, 21519, 150672, 104463, 21, 20631, 9756, 115230, 124194, 9253, 6702, 4654, 122671, 20657, 16049, 125367, 6213, 100185, 111322, 107226, 15583, 21857, 97889, 166372, 21745, 104051, 5114, 151806]}}
|
||||
{"user_anime_list": {"user_anime_list": [166531, 98437, 269, 104462, 21519, 150672, 104463, 21, 20631, 9756, 115230, 124194, 9253, 6702, 4654, 122671, 16049, 125367, 6213, 100185, 111322, 107226, 5081, 15583, 21857, 97889, 166372, 21745, 104051, 5114, 151806]}}
|
||||
Reference in New Issue
Block a user