mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-27 21:23:39 -08:00
completed the basic ui and anilist module
This commit is contained in:
2
app/Utility/__init__.py
Normal file
2
app/Utility/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .media_card_loader import MediaCardLoader
|
||||
from .show_notification import show_notification
|
||||
256
app/Utility/media_card_loader.py
Normal file
256
app/Utility/media_card_loader.py
Normal file
@@ -0,0 +1,256 @@
|
||||
from html.parser import HTMLParser
|
||||
import os
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
from collections import deque
|
||||
import threading
|
||||
from time import sleep
|
||||
from View.components import MediaCard
|
||||
from pytube import YouTube
|
||||
from kivy.loader import _ThreadPool
|
||||
from kivy.clock import Clock
|
||||
from kivy.cache import Cache
|
||||
|
||||
|
||||
Cache.register("anime")
|
||||
|
||||
"""
|
||||
gotta learn how this works :)
|
||||
"""
|
||||
# class _Worker(Thread):
|
||||
# def __init__(self, pool, tasks):
|
||||
# Thread.__init__(self)
|
||||
# self.tasks = tasks
|
||||
# self.daemon = True
|
||||
# self.pool = pool
|
||||
# self.start()
|
||||
|
||||
# def run(self):
|
||||
# while self.pool.running:
|
||||
# func, args, kwargs = self.tasks.get()
|
||||
# try:
|
||||
# func(*args, **kwargs)
|
||||
# except Exception as e:
|
||||
# print(e)
|
||||
# self.tasks.task_done()
|
||||
|
||||
# class _ThreadPool(object):
|
||||
# '''Pool of threads consuming tasks from a queue
|
||||
# '''
|
||||
# def __init__(self, num_threads):
|
||||
# super(_ThreadPool, self).__init__()
|
||||
# self.running = True
|
||||
# self.tasks = queue.Queue()
|
||||
# for _ in range(num_threads):
|
||||
# _Worker(self, self.tasks)
|
||||
|
||||
# def add_task(self, func, *args, **kargs):
|
||||
# '''Add a task to the queue
|
||||
# '''
|
||||
# self.tasks.put((func, args, kargs))
|
||||
|
||||
# def stop(self):
|
||||
# self.running = False
|
||||
# self.tasks.join()
|
||||
|
||||
user_data = JsonStore("user_data.json")
|
||||
my_list = user_data.get("my_list")["list"] # returns a list of anime ids
|
||||
yt_stream_links = user_data.get("yt_stream_links")["links"]
|
||||
|
||||
if yt_stream_links:
|
||||
for link in yt_stream_links:
|
||||
Cache.append("anime",link[0],tuple(link[1]))
|
||||
|
||||
# for youtube video links gotten from from pytube which is blocking
|
||||
class MediaCardDataLoader(object):
|
||||
def __init__(self):
|
||||
self._resume_cond = threading.Condition()
|
||||
self._num_workers = 5
|
||||
self._max_upload_per_frame = 5
|
||||
self._paused = False
|
||||
self._q_load = deque()
|
||||
self._q_done = deque()
|
||||
self._client = []
|
||||
self._running = False
|
||||
self._start_wanted = False
|
||||
self._trigger_update = Clock.create_trigger(self._update)
|
||||
def start(self):
|
||||
'''Start the loader thread/process.'''
|
||||
self._running = True
|
||||
|
||||
def run(self, *largs):
|
||||
'''Main loop for the loader.'''
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
'''Stop the loader thread/process.'''
|
||||
self._running = False
|
||||
|
||||
def pause(self):
|
||||
'''Pause the loader, can be useful during interactions.
|
||||
|
||||
.. versionadded:: 1.6.0
|
||||
'''
|
||||
self._paused = True
|
||||
|
||||
def resume(self):
|
||||
'''Resume the loader, after a :meth:`pause`.
|
||||
|
||||
.. versionadded:: 1.6.0
|
||||
'''
|
||||
self._paused = False
|
||||
self._resume_cond.acquire()
|
||||
self._resume_cond.notify_all()
|
||||
self._resume_cond.release()
|
||||
|
||||
def _wait_for_resume(self):
|
||||
while self._running and self._paused:
|
||||
self._resume_cond.acquire()
|
||||
self._resume_cond.wait(0.25)
|
||||
self._resume_cond.release()
|
||||
|
||||
def cached_fetch_data(self,yt_watch_url):
|
||||
data:tuple = Cache.get("anime",yt_watch_url) # type: ignore # trailer_url is the yt_watch_link
|
||||
if not data[0]:
|
||||
yt = YouTube(yt_watch_url)
|
||||
preview_image = yt.thumbnail_url
|
||||
try:
|
||||
video_stream_url = yt.streams.filter(progressive=True,file_extension="mp4")[-1].url
|
||||
# sleep(0.5)
|
||||
data = preview_image,video_stream_url
|
||||
yt_stream_links.append((yt_watch_url,data))
|
||||
user_data.put("yt_stream_links",links=yt_stream_links)
|
||||
except:
|
||||
data = preview_image,None
|
||||
return data
|
||||
|
||||
def _load(self, kwargs):
|
||||
while len(self._q_done) >= (
|
||||
self._max_upload_per_frame * self._num_workers):
|
||||
sleep(0.1) # type: ignore
|
||||
self._wait_for_resume()
|
||||
yt_watch_link = kwargs['yt_watch_link']
|
||||
try:
|
||||
data = self.cached_fetch_data(yt_watch_link)
|
||||
print("Update: ",data)
|
||||
except Exception as e:
|
||||
data = None
|
||||
print(f"Not accesible:{e} ")
|
||||
|
||||
self._q_done.appendleft((yt_watch_link, data))
|
||||
self._trigger_update()
|
||||
def _update(self,*largs):
|
||||
if self._start_wanted:
|
||||
if not self._running:
|
||||
self.start()
|
||||
self._start_wanted = False
|
||||
|
||||
# in pause mode, don't unqueue anything.
|
||||
if self._paused:
|
||||
self._trigger_update()
|
||||
return
|
||||
|
||||
for _ in range(self._max_upload_per_frame):
|
||||
try:
|
||||
yt_watch_link, data= self._q_done.pop()
|
||||
except IndexError:
|
||||
return
|
||||
# update client
|
||||
for c_yt_watch_link, client in self._client[:]:
|
||||
if yt_watch_link != c_yt_watch_link:
|
||||
continue
|
||||
|
||||
# got one client to update
|
||||
if data:
|
||||
# client.set_preview_image(data[0])
|
||||
trailer_url = data[1]
|
||||
print(trailer_url,"-----------------")
|
||||
if trailer_url:
|
||||
client.set_trailer_url(trailer_url)
|
||||
print(client.title,self._running)
|
||||
Cache.append("anime",yt_watch_link,data)
|
||||
self._client.remove((c_yt_watch_link, client))
|
||||
|
||||
self._trigger_update()
|
||||
|
||||
def media_card(self,anime_item,load_callback=None, post_callback=None,
|
||||
**kwargs):
|
||||
|
||||
media_card = MediaCard()
|
||||
media_card.anime_id = anime_item["id"]
|
||||
if anime_item["title"]["english"]:
|
||||
media_card.title = anime_item["title"]["english"]
|
||||
else:
|
||||
media_card.title = anime_item["title"]["romaji"]
|
||||
# if anime_item.get("cover_image"):
|
||||
media_card.cover_image_url = anime_item["coverImage"]["medium"]
|
||||
media_card.popularity = str(anime_item["popularity"])
|
||||
media_card.favourites = str(anime_item["favourites"])
|
||||
media_card.episodes = str(anime_item["episodes"])
|
||||
if anime_item.get("description"):
|
||||
media_card.description = anime_item["description"]
|
||||
media_card.first_aired_on = f'{anime_item["startDate"]["day"]}-{anime_item["startDate"]["month"]}-{anime_item["startDate"]["year"]}'
|
||||
media_card.studios = ", ".join([studio["name"] for studio in anime_item["studios"]["nodes"]])
|
||||
if anime_item.get("tags"):
|
||||
media_card.tags = ", ".join([tag["name"] for tag in anime_item["tags"]])
|
||||
media_card.media_status = anime_item["status"]
|
||||
if anime_item.get("genres"):
|
||||
media_card.genres = ",".join(anime_item["genres"])
|
||||
# media_card.characters =
|
||||
if anime_item["id"] in my_list:
|
||||
media_card.is_in_my_list = True
|
||||
if anime_item["averageScore"]:
|
||||
stars = int(anime_item["averageScore"]/100*6)
|
||||
if stars:
|
||||
for i in range(stars):
|
||||
media_card.stars[i] = 1
|
||||
|
||||
if anime_item["trailer"]:
|
||||
yt_watch_link = "https://youtube.com/watch?v="+anime_item["trailer"]["id"]
|
||||
data = Cache.get("anime",yt_watch_link) # type: ignore # trailer_url is the yt_watch_link
|
||||
if data:
|
||||
if data[1] not in (None,False):
|
||||
media_card.set_preview_image(data[0])
|
||||
media_card.set_trailer_url(data[1])
|
||||
return media_card
|
||||
else:
|
||||
# if data is None, this is really the first time
|
||||
self._client.append((yt_watch_link,media_card))
|
||||
self._q_load.appendleft({
|
||||
'yt_watch_link': yt_watch_link,
|
||||
'load_callback': load_callback,
|
||||
'post_callback': post_callback,
|
||||
'current_anime':anime_item["id"],
|
||||
'kwargs': kwargs})
|
||||
if not kwargs.get('nocache', False):
|
||||
Cache.append('anime',yt_watch_link, (False,False))
|
||||
self._start_wanted = True
|
||||
self._trigger_update()
|
||||
return media_card
|
||||
|
||||
|
||||
class LoaderThreadPool(MediaCardDataLoader):
|
||||
def __init__(self):
|
||||
super(LoaderThreadPool, self).__init__()
|
||||
self.pool:_ThreadPool|None = None
|
||||
|
||||
def start(self):
|
||||
super(LoaderThreadPool, self).start()
|
||||
self.pool = _ThreadPool(self._num_workers)
|
||||
Clock.schedule_interval(self.run, 0)
|
||||
|
||||
def stop(self):
|
||||
super(LoaderThreadPool, self).stop()
|
||||
Clock.unschedule(self.run)
|
||||
self.pool.stop()
|
||||
|
||||
def run(self, *largs):
|
||||
while self._running:
|
||||
try:
|
||||
parameters = self._q_load.pop()
|
||||
except:
|
||||
return
|
||||
self.pool.add_task(self._load, parameters)
|
||||
|
||||
MediaCardLoader = LoaderThreadPool()
|
||||
# Logger.info('Loader: using a thread pool of {} workers'.format(
|
||||
# Loader.num_workers))
|
||||
17
app/Utility/observer.py
Normal file
17
app/Utility/observer.py
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
# Of course, "very flexible Python" allows you to do without an abstract
|
||||
# superclass at all or use the clever exception `NotImplementedError`. In my
|
||||
# opinion, this can negatively affect the architecture of the application.
|
||||
# I would like to point out that using Kivy, one could use the on-signaling
|
||||
# model. In this case, when the state changes, the model will send a signal
|
||||
# that can be received by all attached observers. This approach seems less
|
||||
# universal - you may want to use a different library in the future.
|
||||
|
||||
|
||||
class Observer:
|
||||
"""Abstract superclass for all observers."""
|
||||
|
||||
def model_is_changed(self):
|
||||
"""
|
||||
The method that will be called on the observer when the model changes.
|
||||
"""
|
||||
22
app/Utility/show_notification.py
Normal file
22
app/Utility/show_notification.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from kivymd.uix.snackbar import MDSnackbar,MDSnackbarText,MDSnackbarSupportingText
|
||||
from kivy.clock import Clock
|
||||
|
||||
def show_notification(title,details):
|
||||
def _show(dt):
|
||||
MDSnackbar(
|
||||
MDSnackbarText(
|
||||
text=title,
|
||||
),
|
||||
MDSnackbarSupportingText(
|
||||
text=details,
|
||||
shorten=False,
|
||||
max_lines=0,
|
||||
adaptive_height=True
|
||||
),
|
||||
duration=5,
|
||||
y="10dp",
|
||||
pos_hint={"bottom": 1,"right":.99},
|
||||
padding=[0, 0, "8dp", "8dp"],
|
||||
size_hint_x=.4
|
||||
).open()
|
||||
Clock.schedule_once(_show,1)
|
||||
Reference in New Issue
Block a user