perf(media card): use recyleboxlayout for more efficiency and better performance

This commit is contained in:
Benex254
2024-08-05 09:46:56 +03:00
parent db7b38ffac
commit 2e2572db3f
10 changed files with 197 additions and 66 deletions

View File

@@ -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")

View 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)

View 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)

View File

@@ -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):

View File

@@ -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"

View File

@@ -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)

View File

@@ -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"

View File

@@ -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.

View File

@@ -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)

View File

@@ -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()