mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-25 20:34:26 -08:00
perf(media card): use recyleboxlayout for more efficiency and better performance
This commit is contained in:
@@ -35,8 +35,9 @@ class HomeScreenController:
|
||||
most_popular_cards_generator = self.model.get_most_popular_anime()
|
||||
if isgenerator(most_popular_cards_generator):
|
||||
for card in most_popular_cards_generator:
|
||||
card.screen = self.view
|
||||
most_popular_cards_container.container.add_widget(card)
|
||||
card["screen"] = self.view
|
||||
card["viewclass"] = "MediaCard"
|
||||
most_popular_cards_container.container.data.append(card)
|
||||
self.view.main_container.add_widget(most_popular_cards_container)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load most popular anime")
|
||||
@@ -48,8 +49,9 @@ class HomeScreenController:
|
||||
most_favourite_cards_generator = self.model.get_most_favourite_anime()
|
||||
if isgenerator(most_favourite_cards_generator):
|
||||
for card in most_favourite_cards_generator:
|
||||
card.screen = self.view
|
||||
most_favourite_cards_container.container.add_widget(card)
|
||||
card["screen"] = self.view
|
||||
card["viewclass"] = "MediaCard"
|
||||
most_favourite_cards_container.container.data.append(card)
|
||||
self.view.main_container.add_widget(most_favourite_cards_container)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load most favourite anime")
|
||||
@@ -61,8 +63,9 @@ class HomeScreenController:
|
||||
trending_cards_generator = self.model.get_trending_anime()
|
||||
if isgenerator(trending_cards_generator):
|
||||
for card in trending_cards_generator:
|
||||
card.screen = self.view
|
||||
trending_cards_container.container.add_widget(card)
|
||||
card["screen"] = self.view
|
||||
card["viewclass"] = "MediaCard"
|
||||
trending_cards_container.container.data.append(card)
|
||||
self.view.main_container.add_widget(trending_cards_container)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load trending anime")
|
||||
@@ -74,8 +77,9 @@ class HomeScreenController:
|
||||
most_scored_cards_generator = self.model.get_most_scored_anime()
|
||||
if isgenerator(most_scored_cards_generator):
|
||||
for card in most_scored_cards_generator:
|
||||
card.screen = self.view
|
||||
most_scored_cards_container.container.add_widget(card)
|
||||
card["screen"] = self.view
|
||||
card["viewclass"] = "MediaCard"
|
||||
most_scored_cards_container.container.data.append(card)
|
||||
self.view.main_container.add_widget(most_scored_cards_container)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load highest scored anime")
|
||||
@@ -89,8 +93,9 @@ class HomeScreenController:
|
||||
)
|
||||
if isgenerator(most_recently_updated_cards_generator):
|
||||
for card in most_recently_updated_cards_generator:
|
||||
card.screen = self.view
|
||||
most_recently_updated_cards_container.container.add_widget(card)
|
||||
card["screen"] = self.view
|
||||
card["viewclass"] = "MediaCard"
|
||||
most_recently_updated_cards_container.container.data.append(card)
|
||||
self.view.main_container.add_widget(most_recently_updated_cards_container)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load recently updated anime")
|
||||
@@ -102,8 +107,9 @@ class HomeScreenController:
|
||||
upcoming_cards_generator = self.model.get_upcoming_anime()
|
||||
if isgenerator(upcoming_cards_generator):
|
||||
for card in upcoming_cards_generator:
|
||||
card.screen = self.view
|
||||
upcoming_cards_container.container.add_widget(card)
|
||||
card["screen"] = self.view
|
||||
card["viewclass"] = "MediaCard"
|
||||
upcoming_cards_container.container.data.append(card)
|
||||
self.view.main_container.add_widget(upcoming_cards_container)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load upcoming anime")
|
||||
|
||||
38
fastanime/Utility/downloader/downloader.py
Normal file
38
fastanime/Utility/downloader/downloader.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import yt_dlp
|
||||
|
||||
|
||||
class MyLogger:
|
||||
def debug(self, msg):
|
||||
print(msg)
|
||||
|
||||
def warning(self, msg):
|
||||
print(msg)
|
||||
|
||||
def error(self, msg):
|
||||
print(msg)
|
||||
|
||||
|
||||
def my_hook(d):
|
||||
if d["status"] == "finished":
|
||||
print("Done downloading, now converting ...")
|
||||
|
||||
|
||||
# URL of the file you want to download
|
||||
url = "http://example.com/path/to/file.mp4"
|
||||
|
||||
# Options for yt-dlp
|
||||
ydl_opts = {
|
||||
"outtmpl": "/path/to/downloaded/file.%(ext)s", # Specify the output path and template
|
||||
"logger": MyLogger(), # Custom logger
|
||||
"progress_hooks": [my_hook], # Progress hook
|
||||
}
|
||||
|
||||
|
||||
# Function to download the file
|
||||
def download_file(url, options):
|
||||
with yt_dlp.YoutubeDL(options) as ydl:
|
||||
ydl.download([url])
|
||||
|
||||
|
||||
# Call the function
|
||||
download_file(url, ydl_opts)
|
||||
39
fastanime/Utility/downloader/hsl.py
Normal file
39
fastanime/Utility/downloader/hsl.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import yt_dlp
|
||||
|
||||
|
||||
class MyLogger:
|
||||
def debug(self, msg):
|
||||
print(msg)
|
||||
|
||||
def warning(self, msg):
|
||||
print(msg)
|
||||
|
||||
def error(self, msg):
|
||||
print(msg)
|
||||
|
||||
|
||||
def my_hook(d):
|
||||
if d["status"] == "finished":
|
||||
print("Done downloading, now converting ...")
|
||||
|
||||
|
||||
# URL of the HLS stream
|
||||
url = "https://example.com/path/to/stream.m3u8"
|
||||
|
||||
# Options for yt-dlp
|
||||
ydl_opts = {
|
||||
"format": "best", # Choose the best quality available
|
||||
"outtmpl": "/path/to/downloaded/video.%(ext)s", # Specify the output path and template
|
||||
"logger": MyLogger(), # Custom logger
|
||||
"progress_hooks": [my_hook], # Progress hook
|
||||
}
|
||||
|
||||
|
||||
# Function to download the HLS video
|
||||
def download_hls_video(url, options):
|
||||
with yt_dlp.YoutubeDL(options) as ydl:
|
||||
ydl.download([url])
|
||||
|
||||
|
||||
# Call the function
|
||||
download_hls_video(url, ydl_opts)
|
||||
@@ -4,7 +4,6 @@ from pytube import YouTube
|
||||
|
||||
from ..libs.anilist.anilist_data_schema import AnilistBaseMediaDataSchema
|
||||
from ..Utility import anilist_data_helper, user_data_helper
|
||||
from ..View.components import MediaCard
|
||||
|
||||
Cache.register("trailer_urls.anime", timeout=360)
|
||||
|
||||
@@ -16,34 +15,35 @@ class MediaCardDataLoader(object):
|
||||
self,
|
||||
anime_item: AnilistBaseMediaDataSchema,
|
||||
):
|
||||
media_card = MediaCard()
|
||||
media_card.anime_id = anime_id = anime_item["id"]
|
||||
media_card_data = {}
|
||||
media_card_data["anime_id"] = anime_id = anime_item["id"]
|
||||
|
||||
# TODO: ADD language preference
|
||||
if anime_item["title"].get("english"):
|
||||
media_card.title = anime_item["title"]["english"]
|
||||
media_card_data["title"] = anime_item["title"]["english"]
|
||||
else:
|
||||
media_card.title = anime_item["title"]["romaji"]
|
||||
media_card_data["title"] = anime_item["title"]["romaji"]
|
||||
|
||||
media_card.cover_image_url = anime_item["coverImage"]["medium"]
|
||||
media_card_data["cover_image_url"] = anime_item["coverImage"]["medium"]
|
||||
|
||||
media_card.popularity = str(anime_item["popularity"])
|
||||
media_card_data["popularity"] = str(anime_item["popularity"])
|
||||
|
||||
media_card.favourites = str(anime_item["favourites"])
|
||||
media_card_data["favourites"] = str(anime_item["favourites"])
|
||||
|
||||
media_card.episodes = str(anime_item["episodes"])
|
||||
media_card_data["episodes"] = str(anime_item["episodes"])
|
||||
|
||||
if anime_item.get("description"):
|
||||
media_card.description = anime_item["description"]
|
||||
media_card_data["description"] = anime_item["description"]
|
||||
else:
|
||||
media_card.description = "None"
|
||||
media_card_data["description"] = "None"
|
||||
|
||||
# TODO: switch to season and year
|
||||
media_card.first_aired_on = (
|
||||
#
|
||||
media_card_data["first_aired_on"] = (
|
||||
f'{anilist_data_helper.format_anilist_date_object(anime_item["startDate"])}'
|
||||
)
|
||||
|
||||
media_card.studios = anilist_data_helper.format_list_data_with_comma(
|
||||
media_card_data["studios"] = anilist_data_helper.format_list_data_with_comma(
|
||||
[
|
||||
studio["name"]
|
||||
for studio in anime_item["studios"]["nodes"]
|
||||
@@ -51,7 +51,7 @@ class MediaCardDataLoader(object):
|
||||
]
|
||||
)
|
||||
|
||||
media_card.producers = anilist_data_helper.format_list_data_with_comma(
|
||||
media_card_data["producers"] = anilist_data_helper.format_list_data_with_comma(
|
||||
[
|
||||
studio["name"]
|
||||
for studio in anime_item["studios"]["nodes"]
|
||||
@@ -59,36 +59,39 @@ class MediaCardDataLoader(object):
|
||||
]
|
||||
)
|
||||
|
||||
media_card.next_airing_episode = "{}".format(
|
||||
media_card_data["next_airing_episode"] = "{}".format(
|
||||
anilist_data_helper.extract_next_airing_episode(
|
||||
anime_item["nextAiringEpisode"]
|
||||
)
|
||||
)
|
||||
if anime_item.get("tags"):
|
||||
media_card.tags = anilist_data_helper.format_list_data_with_comma(
|
||||
media_card_data["tags"] = anilist_data_helper.format_list_data_with_comma(
|
||||
[tag["name"] for tag in anime_item["tags"]]
|
||||
)
|
||||
|
||||
media_card.media_status = anime_item["status"]
|
||||
media_card_data["media_status"] = anime_item["status"]
|
||||
|
||||
if anime_item.get("genres"):
|
||||
media_card.genres = anilist_data_helper.format_list_data_with_comma(
|
||||
media_card_data["genres"] = anilist_data_helper.format_list_data_with_comma(
|
||||
anime_item["genres"]
|
||||
)
|
||||
|
||||
if anime_id in user_data_helper.get_user_anime_list():
|
||||
media_card.is_in_my_list = True
|
||||
media_card_data["is_in_my_list"] = True
|
||||
|
||||
if anime_item["averageScore"]:
|
||||
stars = int(anime_item["averageScore"] / 100 * 6)
|
||||
media_card_data["stars"] = [0, 0, 0, 0, 0, 0]
|
||||
if stars:
|
||||
for i in range(stars):
|
||||
media_card.stars[i] = 1
|
||||
media_card_data["stars"][i] = 1
|
||||
|
||||
if trailer := anime_item.get("trailer"):
|
||||
trailer_url = "https://youtube.com/watch/v=" + trailer["id"]
|
||||
media_card._trailer_url = trailer_url
|
||||
return media_card
|
||||
media_card_data["_trailer_url"] = trailer_url
|
||||
else:
|
||||
media_card_data["_trailer_url"] = ""
|
||||
return media_card_data
|
||||
|
||||
def get_trailer_from_pytube(self, trailer_url, anime):
|
||||
if trailer := Cache.get("trailer_urls.anime", trailer_url):
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
<MediaCardsContainer>
|
||||
size_hint:1,None
|
||||
height:max(self.minimum_height,dp(350),container.minimum_height)
|
||||
height: dp(250)
|
||||
container:container
|
||||
orientation: 'vertical'
|
||||
padding:"10dp"
|
||||
spacing:"5dp"
|
||||
MDLabel:
|
||||
bold:True
|
||||
adaptive_height:True
|
||||
text:root.list_name
|
||||
MDScrollView:
|
||||
size_hint:1,None
|
||||
height:container.minimum_height
|
||||
MDBoxLayout:
|
||||
MDRecycleView:
|
||||
id:container
|
||||
spacing:"10dp"
|
||||
padding:"0dp","10dp","100dp","10dp"
|
||||
size_hint:None,None
|
||||
height:self.minimum_height
|
||||
width:self.minimum_width
|
||||
key_viewclass:"viewclass"
|
||||
key_size:"width"
|
||||
RecycleBoxLayout:
|
||||
size_hint:None,1
|
||||
width:self.minimum_width
|
||||
default_size_hint:None, None
|
||||
default_size:dp(150),dp(100)
|
||||
spacing:"10dp"
|
||||
padding:"0dp","10dp","100dp","10dp"
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from kivy.uix.videoplayer import VideoPlayer
|
||||
|
||||
|
||||
# TODO: make fullscreen exp better
|
||||
class MediaPopupVideoPlayer(VideoPlayer):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
# FIXME: find way to make fullscreen stable
|
||||
#
|
||||
self.allow_fullscreen = False
|
||||
|
||||
def on_fullscreen(self, instance, value):
|
||||
super().on_fullscreen(instance, value)
|
||||
|
||||
@@ -27,9 +27,10 @@
|
||||
height: dp(280)
|
||||
line_color:root.caller.has_trailer_color
|
||||
line_width:1
|
||||
# TODO: Remove the test source
|
||||
MediaPopupVideoPlayer:
|
||||
id:player
|
||||
source: root.caller.trailer_url
|
||||
source: root.caller.trailer_url if root.caller.trailer_url else 'https://www088.vipanicdn.net/streamhls/abae70787c7bd2fcd4fab986c2a5aeba/ep.7.1703900604.m3u8'
|
||||
thumbnail:app.default_anime_image
|
||||
state:"play" if root.caller.trailer_url else "stop"
|
||||
# fit_mode:"fill"
|
||||
|
||||
@@ -59,7 +59,7 @@ class MediaPopup(
|
||||
self.caller = caller
|
||||
|
||||
def on_caller(self, *args):
|
||||
Clock.schedule_once(lambda _: self.apply_class_lang_rules(), -1)
|
||||
self.apply_class_lang_rules()
|
||||
|
||||
def open(self, *_args, **kwargs):
|
||||
"""Display the modal in the Window.
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
<MediaCard>
|
||||
adaptive_height:True
|
||||
spacing:"5dp"
|
||||
image:"https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/bx163270-oxwgbe43Cpog.jpg"
|
||||
size_hint_x: None
|
||||
width:dp(100)
|
||||
on_release:
|
||||
self.open()
|
||||
FitImage:
|
||||
source:root.cover_image_url
|
||||
fit_mode:"fill"
|
||||
@@ -14,6 +11,8 @@
|
||||
height: dp(150)
|
||||
MDDivider:
|
||||
color:root.theme_cls.primaryColor if root._trailer_url else [0.5, 0.5, 0.5, 0.5]
|
||||
size_hint: None, 1
|
||||
width: dp(100)
|
||||
SingleLineLabel:
|
||||
font_style:"Label"
|
||||
role:"medium"
|
||||
@@ -21,4 +20,7 @@
|
||||
max_lines:2
|
||||
halign:"center"
|
||||
color:self.theme_cls.secondaryColor
|
||||
size_hint_x: None
|
||||
shorten:True
|
||||
width: dp(100)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from kivy.clock import Clock
|
||||
from kivy.factory import Factory
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ListProperty,
|
||||
@@ -6,7 +7,6 @@ from kivy.properties import (
|
||||
ObjectProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import HoverBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
@@ -14,7 +14,7 @@ from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from .components.media_popup import MediaPopup
|
||||
|
||||
|
||||
class MediaCard(ButtonBehavior, HoverBehavior, MDBoxLayout):
|
||||
class MediaCard(HoverBehavior, MDBoxLayout):
|
||||
screen = ObjectProperty()
|
||||
anime_id = NumericProperty()
|
||||
title = StringProperty()
|
||||
@@ -38,6 +38,7 @@ class MediaCard(ButtonBehavior, HoverBehavior, MDBoxLayout):
|
||||
cover_image_url = StringProperty()
|
||||
preview_image = StringProperty()
|
||||
has_trailer_color = ListProperty([0.5, 0.5, 0.5, 0.5])
|
||||
_popup_opened = False
|
||||
|
||||
def __init__(self, trailer_url=None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -49,6 +50,38 @@ class MediaCard(ButtonBehavior, HoverBehavior, MDBoxLayout):
|
||||
self.trailer_url = trailer_url
|
||||
self.adaptive_size = True
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if touch.is_mouse_scrolling:
|
||||
return False
|
||||
if not self.collide_point(touch.x, touch.y):
|
||||
return False
|
||||
if self in touch.ud:
|
||||
return False
|
||||
|
||||
if not self.app:
|
||||
return False
|
||||
disabled = True
|
||||
# FIXME: double tap not working
|
||||
#
|
||||
if not disabled and touch.is_double_tap:
|
||||
self.app.show_anime_screen(self.anime_id, self.screen.name)
|
||||
touch.grab(self)
|
||||
touch.ud[self] = True
|
||||
self.last_touch = touch
|
||||
|
||||
elif self.collide_point(*touch.pos):
|
||||
if not self._popup_opened:
|
||||
Clock.schedule_once(self.open)
|
||||
self._popup_opened = True
|
||||
touch.grab(self)
|
||||
touch.ud[self] = True
|
||||
self.last_touch = touch
|
||||
return True
|
||||
else:
|
||||
super().on_touch_down(touch)
|
||||
|
||||
# FIXME: Figure a good way implement this
|
||||
# for now its debugy so scraping it till fix
|
||||
def on_enter(self):
|
||||
def _open_popup(dt):
|
||||
if self.hovering:
|
||||
@@ -59,15 +92,16 @@ class MediaCard(ButtonBehavior, HoverBehavior, MDBoxLayout):
|
||||
return
|
||||
self.open()
|
||||
|
||||
Clock.schedule_once(_open_popup, 5)
|
||||
# Clock.schedule_once(_open_popup, 5)
|
||||
|
||||
def on_popup_open(self, popup: MediaPopup):
|
||||
popup.center = self.center
|
||||
|
||||
def on_dismiss(self, popup: MediaPopup):
|
||||
popup.player.state = "stop"
|
||||
if popup.player._video:
|
||||
popup.player._video.unload()
|
||||
self._popup_opened = False
|
||||
# if popup.player._video:
|
||||
# popup.player._video.unload()
|
||||
|
||||
def set_preview_image(self, image):
|
||||
self.preview_image = image
|
||||
@@ -84,20 +118,19 @@ class MediaCard(ButtonBehavior, HoverBehavior, MDBoxLayout):
|
||||
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
|
||||
def _get_trailer(dt):
|
||||
if trailer := self._trailer_url:
|
||||
# trailer stuff
|
||||
from ....Utility.media_card_loader import media_card_loader
|
||||
|
||||
if trailer := self._trailer_url:
|
||||
# from ....Utility import show_notification
|
||||
if trailer_url := media_card_loader.get_trailer_from_pytube(
|
||||
trailer, self.title
|
||||
):
|
||||
self.trailer_url = trailer_url
|
||||
else:
|
||||
self._trailer_url = ""
|
||||
|
||||
# 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 = ""
|
||||
Clock.schedule_once(_get_trailer, 1)
|
||||
|
||||
# ---------------respond to user actions and call appropriate model-------------------------
|
||||
def on_is_in_my_list(self, instance, in_user_anime_list):
|
||||
@@ -111,6 +144,9 @@ class MediaCard(ButtonBehavior, HoverBehavior, MDBoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
Factory.register("MediaCard", MediaCard)
|
||||
|
||||
|
||||
class MediaCardsContainer(MDBoxLayout):
|
||||
container = ObjectProperty()
|
||||
list_name = StringProperty()
|
||||
|
||||
Reference in New Issue
Block a user