223 lines
8.5 KiB
Python
223 lines
8.5 KiB
Python
import logging
|
|
import os
|
|
|
|
import click
|
|
import requests_cache
|
|
|
|
from anime_downloader import session, util
|
|
from anime_downloader.__version__ import __version__
|
|
from anime_downloader.sites import get_anime_class, ALL_ANIME_SITES, exceptions
|
|
from anime_downloader import animeinfo
|
|
from anime_downloader.config import Config
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
echo = click.echo
|
|
sitenames = [v[1] for v in ALL_ANIME_SITES]
|
|
|
|
# NOTE: Don't put defaults here. Add them to the dict in config
|
|
|
|
|
|
@click.command()
|
|
@click.argument('anime_url')
|
|
@click.option(
|
|
'--episodes', '-e', 'episode_range', metavar='<int>:<int>',
|
|
help="Range of anime you want to download in the form <start>:<end>")
|
|
@click.option(
|
|
'--play', 'player', metavar='PLAYER',
|
|
help="Streams in the specified player")
|
|
@click.option(
|
|
'--force-download', '-f', is_flag=True,
|
|
help='Force downloads even if file exists')
|
|
@click.option(
|
|
'--provider', '-p',
|
|
help='The anime provider (website) for search.',
|
|
type=click.Choice(sitenames)
|
|
)
|
|
@click.option(
|
|
'--ratio', '-r', type=int,
|
|
help='Ratio used for the auto select in search. 100 means it only auto selects on complete matches. 0 auto selects regardless of how similar the result is.',
|
|
default=50
|
|
)
|
|
@click.option(
|
|
'--url', '-u', type=bool, is_flag=True,
|
|
help="If flag is set, prints the stream url instead of downloading")
|
|
@click.option(
|
|
'--choice', '-c', type=int,
|
|
help='Choice to start downloading given anime number ',
|
|
default=None
|
|
)
|
|
@click.option(
|
|
'--download-metadata', '-dm', is_flag=True,
|
|
help='Download additional metadata')
|
|
@click.option("--skip-fillers", is_flag=True,
|
|
help="Skip downloading of fillers.")
|
|
@click.pass_context
|
|
def command(ctx, anime_url, episode_range, player,
|
|
force_download, provider,
|
|
skip_fillers, ratio, url, choice, download_metadata):
|
|
"""
|
|
dl with fallback providers\n
|
|
Will use another provider even if the chosen one fails.\n
|
|
"""
|
|
|
|
# Borrows some config from the original dl command.
|
|
# This can all be flags, but ezdl is made to be easy.
|
|
fallback_qualities = Config['dl']['fallback_qualities']
|
|
download_dir = Config['dl']['download_dir']
|
|
quality = Config['dl']['quality']
|
|
url = Config['dl']['url'] if not url else url
|
|
skip_download = Config['dl']['skip_download']
|
|
chunk_size = Config['dl']['chunk_size']
|
|
speed_limit = Config['dl']['speed_limit']
|
|
|
|
external_downloader = Config['dl']['external_downloader']
|
|
file_format = Config['ezdl']['file_format']
|
|
fallback_providers = Config['ezdl']['fallback_providers']
|
|
|
|
query = anime_url[:]
|
|
util.print_info(__version__)
|
|
|
|
fallback_providers.insert(0, provider)
|
|
# Eliminates duplicates while keeping order
|
|
providers = sorted(set(fallback_providers), key=fallback_providers.index)
|
|
|
|
info = animeinfo.search_anilist(query, choice)
|
|
|
|
logger.info('Selected "{}" '.format(info.title))
|
|
episode_count = info.episodes - 1
|
|
# Interprets the episode range for use in a for loop.
|
|
# 1:3 -> for _episode in range(1, 4):
|
|
episode_range = util.parse_episode_range(episode_count, episode_range)
|
|
episode_range_split = episode_range.split(':')
|
|
# Issue #508.
|
|
if episode_range_split[0] > episode_range_split[-1]:
|
|
raise exceptions.NotFoundError('No episodes found within index.')
|
|
|
|
# Stores the choices for each provider, to prevent re-prompting search.
|
|
# As the current setup runs episode wise without this a 12 episode series
|
|
# would give 12+ prompts.
|
|
choice_dict = {}
|
|
|
|
# Doesn't work on nyaa since it only returns one episode.
|
|
for episode_range in range(int(episode_range_split[0]), int(
|
|
episode_range_split[-1]) + 1):
|
|
# Exits if all providers are skipped.
|
|
if [choice_dict[i] for i in choice_dict] == [0] * len(providers):
|
|
logger.info('All providers skipped, exiting')
|
|
exit()
|
|
|
|
for provider in providers:
|
|
if not get_anime_class(provider):
|
|
logger.info('"{}" is an invalid provider'.format(provider))
|
|
continue
|
|
|
|
logger.debug('Current provider: {}'.format(provider))
|
|
# TODO: Replace by factory
|
|
cls = get_anime_class(anime_url)
|
|
|
|
# To make the downloads use the correct name if URL:s are used.
|
|
real_provider = cls.sitename if cls else provider
|
|
# This will allow for animeinfo metadata in filename and one
|
|
# filename for multiple providers.
|
|
rep_dict = {
|
|
'animeinfo_anime_title': util.slugify(info.title),
|
|
'provider': util.slugify(real_provider),
|
|
'anime_title': '{anime_title}',
|
|
'ep_no': '{ep_no}'
|
|
}
|
|
fixed_file_format = file_format.format(**rep_dict)
|
|
# Keeping this as I don't know the impact of removing it.
|
|
# It's False by default in normal dl.
|
|
disable_ssl = False
|
|
session.get_session().verify = not disable_ssl
|
|
|
|
# This is just to make choices in providers presistent between
|
|
# searches.
|
|
choice_provider = choice_dict.get(provider)
|
|
|
|
if not cls:
|
|
_anime_url, choice_provider = util.search(
|
|
anime_url, provider, val=choice_provider, season_info=info, ratio=ratio)
|
|
choice_dict[provider] = choice_provider
|
|
if choice_provider == 0 or not _anime_url:
|
|
logger.info('Skipped')
|
|
continue
|
|
|
|
cls = get_anime_class(_anime_url)
|
|
|
|
try:
|
|
anime = cls(_anime_url, quality=quality,
|
|
fallback_qualities=fallback_qualities)
|
|
# I have yet to investigate all errors this can output
|
|
# No sources found gives error which exits the script
|
|
except BaseException:
|
|
continue
|
|
|
|
logger.debug('Found anime: {}'.format(anime.title))
|
|
|
|
try:
|
|
animes = util.parse_ep_str(anime, str(episode_range))
|
|
except RuntimeError:
|
|
logger.error(
|
|
'No episode found with index {}'.format(episode_range))
|
|
continue
|
|
except:
|
|
logger.error('Unknown provider error')
|
|
continue
|
|
|
|
# TODO:
|
|
# Two types of plugins:
|
|
# - Aime plugin: Pass the whole anime
|
|
# - Ep plugin: Pass each episode
|
|
if url or player:
|
|
skip_download = True
|
|
|
|
if download_dir and not skip_download:
|
|
logger.info(
|
|
'Downloading to {}'.format(
|
|
os.path.abspath(download_dir)))
|
|
if skip_fillers:
|
|
fillers = util.get_filler_episodes(query)
|
|
for episode in animes:
|
|
if skip_fillers and fillers:
|
|
if episode.ep_no in fillers:
|
|
logger.info(
|
|
"Skipping episode {} because it is a filler.".format(
|
|
episode.ep_no))
|
|
continue
|
|
|
|
if download_metadata:
|
|
util.download_metadata(
|
|
fixed_file_format, info.metadata, episode)
|
|
|
|
if url:
|
|
util.print_episodeurl(episode)
|
|
|
|
if player:
|
|
util.play_episode(
|
|
episode,
|
|
player=player,
|
|
title=f'{anime.title} - Episode {episode.ep_no}')
|
|
|
|
if not skip_download:
|
|
if external_downloader:
|
|
logging.info('Downloading episode {} of {}'.format(
|
|
episode.ep_no, anime.title)
|
|
)
|
|
util.external_download(external_downloader, episode,
|
|
fixed_file_format, path=download_dir, speed_limit=speed_limit)
|
|
continue
|
|
if chunk_size is not None:
|
|
chunk_size = int(chunk_size)
|
|
chunk_size *= 1e6
|
|
with requests_cache.disabled():
|
|
episode.download(force=force_download,
|
|
path=download_dir,
|
|
format=fixed_file_format,
|
|
range_size=chunk_size)
|
|
print()
|
|
|
|
# If it's all successful proceeds to next ep instead of looping.
|
|
break
|