248 lines
6.7 KiB
Python
248 lines
6.7 KiB
Python
"""
|
|
anime.py contains the base classes required for other anime classes.
|
|
"""
|
|
import requests
|
|
from bs4 import BeautifulSoup
|
|
|
|
import os
|
|
import logging
|
|
import copy
|
|
|
|
from anime_downloader.sites.exceptions import AnimeDLError, NotFoundError
|
|
from anime_downloader import util
|
|
from anime_downloader.extractors import get_extractor
|
|
from anime_downloader.downloader import get_downloader
|
|
|
|
|
|
class Anime:
|
|
"""
|
|
Base class for all anime classes.
|
|
|
|
Parameters
|
|
----------
|
|
url: string
|
|
URL of the anime.
|
|
quality: One of ['360p', '480p', '720p', '1080p']
|
|
Quality of episodes
|
|
fallback_qualities: list
|
|
The order of fallback.
|
|
|
|
Attributes
|
|
----------
|
|
sitename: string
|
|
name of the site
|
|
title: string
|
|
Title of the anime
|
|
meta: dict
|
|
metadata about the anime. [Can be empty]
|
|
QUALITIES: list
|
|
Possible qualities for the site
|
|
"""
|
|
sitename = ''
|
|
title = ''
|
|
meta = dict()
|
|
subclasses = {}
|
|
|
|
QUALITIES = None
|
|
_episodeClass = object
|
|
|
|
@classmethod
|
|
def search(cls, query):
|
|
"""
|
|
Search searches for the anime using the query given.
|
|
|
|
query :
|
|
query is
|
|
"""
|
|
return
|
|
|
|
def __init__(self, url=None, quality='720p',
|
|
fallback_qualities=['720p', '480p', '360p'],
|
|
_skip_online_data=False):
|
|
self.url = url
|
|
self._fallback_qualities = fallback_qualities
|
|
|
|
if quality in self.QUALITIES:
|
|
self.quality = quality
|
|
else:
|
|
raise AnimeDLError(
|
|
'Quality {0} not found in {1}'.format(quality, self.QUALITIES))
|
|
|
|
if not _skip_online_data:
|
|
logging.info('Extracting episode info from page')
|
|
self.get_data()
|
|
|
|
@classmethod
|
|
def verify_url(self, url):
|
|
if self.sitename in url:
|
|
return True
|
|
return False
|
|
|
|
def __init_subclass__(cls, sitename, **kwargs):
|
|
super().__init_subclass__(**kwargs)
|
|
cls.subclasses[sitename] = cls
|
|
|
|
@classmethod
|
|
def factory(cls, sitename: str):
|
|
return cls.subclasses[sitename]
|
|
|
|
def get_data(self):
|
|
self._episode_urls = []
|
|
try:
|
|
self._scrape_metadata()
|
|
except Exception as e:
|
|
logging.debug('Metadata scraping error: {}'.format(e))
|
|
|
|
self._episode_urls = self._scrape_episodes()
|
|
self._len = len(self._episode_urls)
|
|
|
|
logging.debug('EPISODE IDS: length: {}, ids: {}'.format(
|
|
self._len, self._episode_urls))
|
|
|
|
self._episode_urls = [(no+1, id) for no, id in
|
|
enumerate(self._episode_urls)]
|
|
|
|
return self._episode_urls
|
|
|
|
def __getitem__(self, index):
|
|
episode_class = AnimeEpisode.subclasses[self.sitename]
|
|
if isinstance(index, int):
|
|
ep_id = self._episode_urls[index]
|
|
return episode_class(ep_id[1], self.quality, parent=self,
|
|
ep_no=ep_id[0])
|
|
elif isinstance(index, slice):
|
|
anime = copy.deepcopy(self)
|
|
anime._episode_urls = anime._episode_urls[index]
|
|
return anime
|
|
|
|
def __repr__(self):
|
|
return '''
|
|
Site: {name}
|
|
Anime: {title}
|
|
Episode count: {length}
|
|
'''.format(name=self.sitename, title=self.title, length=len(self))
|
|
|
|
def __len__(self):
|
|
return self._len
|
|
|
|
def __str__(self):
|
|
return self.title
|
|
|
|
def _scrape_episodes(self):
|
|
return
|
|
|
|
def _scrape_metadata(self):
|
|
return
|
|
|
|
|
|
class AnimeEpisode:
|
|
QUALITIES = None
|
|
title = ''
|
|
stream_url = ''
|
|
subclasses = {}
|
|
|
|
def __init__(self, url, quality='720p', parent=None,
|
|
ep_no=None):
|
|
if quality not in self.QUALITIES:
|
|
raise AnimeDLError('Incorrect quality: "{}"'.format(quality))
|
|
|
|
self.ep_no = ep_no
|
|
self.url = url
|
|
self.quality = quality
|
|
self._parent = parent
|
|
self._sources = None
|
|
self.pretty_title = '{}-{}'.format(self._parent.title, self.ep_no)
|
|
|
|
logging.debug("Extracting stream info of id: {}".format(self.url))
|
|
|
|
# TODO: New flag: online_data=False
|
|
try:
|
|
self.get_data()
|
|
# Just to verify the source is acquired
|
|
self.source().stream_url
|
|
except NotFoundError:
|
|
# Issue #28
|
|
qualities = copy.copy(self._parent._fallback_qualities)
|
|
try:
|
|
qualities.remove(self.quality)
|
|
except ValueError:
|
|
pass
|
|
for quality in qualities:
|
|
logging.warning('Quality {} not found. Trying {}.'.format(
|
|
self.quality, quality))
|
|
self.quality = quality
|
|
try:
|
|
self.get_data()
|
|
self.source().stream_url
|
|
# parent.quality = self.quality
|
|
break
|
|
except NotFoundError:
|
|
# Issue #28
|
|
# qualities.remove(self.quality)
|
|
pass
|
|
|
|
def __init_subclass__(cls, sitename: str, **kwargs):
|
|
super().__init_subclass__(**kwargs)
|
|
cls.subclasses[sitename] = cls
|
|
cls.sitename = sitename
|
|
|
|
@classmethod
|
|
def factory(cls, sitename: str):
|
|
return cls.subclasses[sitename]
|
|
|
|
def source(self, index=0):
|
|
if not self._sources:
|
|
self.get_data()
|
|
|
|
try:
|
|
sitename, url = self._sources[index]
|
|
except TypeError:
|
|
return self._sources[index]
|
|
|
|
extractor = get_extractor(sitename)
|
|
ext = extractor(url, quality=self.quality)
|
|
self._sources[index] = ext
|
|
|
|
return ext
|
|
|
|
def get_data(self):
|
|
self._sources = self._get_sources()
|
|
logging.debug('Sources : '.format(self._sources))
|
|
|
|
def _get_sources(self):
|
|
raise NotImplementedError
|
|
|
|
def download(self, force=False, path=None,
|
|
format='{anime_title}_{ep_no}', range_size=None):
|
|
# TODO: Remove this shit
|
|
logging.info('Downloading {}'.format(self.pretty_title))
|
|
if format:
|
|
file_name = util.format_filename(format, self)+'.mp4'
|
|
|
|
if path is None:
|
|
path = './' + file_name
|
|
if path.endswith('.mp4'):
|
|
path = path
|
|
else:
|
|
path = os.path.join(path, file_name)
|
|
|
|
Downloader = get_downloader('http')
|
|
downloader = Downloader(self.source(),
|
|
path, force, range_size=range_size)
|
|
|
|
downloader.download()
|
|
|
|
|
|
class SearchResult:
|
|
def __init__(self, title, url, poster):
|
|
self.title = title
|
|
self.url = url
|
|
self.poster = poster
|
|
self.meta = ''
|
|
|
|
def __repr__(self):
|
|
return '<SearchResult Title: {} URL: {}>'.format(self.title, self.url)
|
|
|
|
def __str__(self):
|
|
return self.title
|