Implement download and play
parent
65e9b5920c
commit
92f1ec4144
21
README.md
21
README.md
|
@ -26,10 +26,16 @@ Usage: anime-dl [OPTIONS] ANIME_URL
|
||||||
Download your favourite anime.
|
Download your favourite anime.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--range TEXT Range of anime you want to download in the form <start>:<end>
|
--range <int>:<int> Range of anime you want to download in the form
|
||||||
--playlist If falaf is set, saves the stream urls in an m3u file
|
<start>:<end>
|
||||||
--url If flag is set, prints the stream url and not download
|
--playlist If flag is set, saves the stream urls in an m3u
|
||||||
--help Show this message and exit.
|
file
|
||||||
|
--url If flag is set, prints the stream url and not
|
||||||
|
download
|
||||||
|
--play PLAYER Streams in the specified player
|
||||||
|
--no-download Retrieve without downloading
|
||||||
|
--quality [360p|480p|720p] Specify the quality of episode. Default-720p.
|
||||||
|
--help Show this message and exit.
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
|
@ -48,7 +54,12 @@ anime-dl 'https://9anime.is/watch/fullmetal-alchemist-brotherhood.0r7/j69y93' --
|
||||||
anime-dl 'https://9anime.is/watch/fullmetal-alchemist-brotherhood.0r7/j69y93' --range 1:21
|
anime-dl 'https://9anime.is/watch/fullmetal-alchemist-brotherhood.0r7/j69y93' --range 1:21
|
||||||
```
|
```
|
||||||
|
|
||||||
- To get stream url of Fullmetal Alchemist: Brotherhood episode 1. Afterwards the stream can be played by `mpv` or `vlc`.
|
- To get stream url of Fullmetal Alchemist: Brotherhood episode 1.
|
||||||
```
|
```
|
||||||
anime-dl 'https://9anime.is/watch/fullmetal-alchemist-brotherhood.0r7/j69y93' --url --range 1
|
anime-dl 'https://9anime.is/watch/fullmetal-alchemist-brotherhood.0r7/j69y93' --url --range 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- To play using vlc. (On windows use path to exe)
|
||||||
|
```
|
||||||
|
anime-dl 'https://9anime.is/watch/fullmetal-alchemist-brotherhood.0r7/j69y93' --play vlc --range 1
|
||||||
|
```
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
QUALITIES = ['360p', '480p', '720p']
|
QUALITIES = ['360p', '480p', '720p']
|
||||||
|
@ -19,13 +22,16 @@ class NotFoundError(AnimeDLError):
|
||||||
|
|
||||||
|
|
||||||
class Anime:
|
class Anime:
|
||||||
def __init__(self, url, quality='720p'):
|
def __init__(self, url, quality='720p', callback=None):
|
||||||
|
|
||||||
if quality not in QUALITIES:
|
if quality not in QUALITIES:
|
||||||
raise AnimeDLError('Incorrect quality: "{}"'.format(quality))
|
raise AnimeDLError('Incorrect quality: "{}"'.format(quality))
|
||||||
|
|
||||||
self.url = url
|
self.url = url
|
||||||
self.quality = quality
|
self.quality = quality
|
||||||
|
self._callback = callback
|
||||||
|
if self._callback:
|
||||||
|
self._callback('Extracting episode info from page')
|
||||||
self.getEpisodes()
|
self.getEpisodes()
|
||||||
|
|
||||||
def getEpisodes(self):
|
def getEpisodes(self):
|
||||||
|
@ -35,6 +41,8 @@ class Anime:
|
||||||
episodes = soup.find_all('ul', ['episodes'])
|
episodes = soup.find_all('ul', ['episodes'])
|
||||||
if episodes == []:
|
if episodes == []:
|
||||||
err = 'No episodes found in url "{}"'.format(self.url)
|
err = 'No episodes found in url "{}"'.format(self.url)
|
||||||
|
if self._callback:
|
||||||
|
self._callback(err)
|
||||||
args = [self.url]
|
args = [self.url]
|
||||||
raise NotFoundError(err, *args)
|
raise NotFoundError(err, *args)
|
||||||
episodes = episodes[:int(len(episodes)/3)]
|
episodes = episodes[:int(len(episodes)/3)]
|
||||||
|
@ -49,31 +57,67 @@ class Anime:
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
ep_id = self._episodeIds[index]
|
ep_id = self._episodeIds[index]
|
||||||
return Episode(ep_id, self.quality)
|
return Episode(ep_id, self.quality, callback=self._callback)
|
||||||
|
|
||||||
|
|
||||||
class Episode:
|
class Episode:
|
||||||
_base_url = r'https://9anime.is/ajax/episode/info?id={0}&server=33'
|
_base_url = r'https://9anime.is/ajax/episode/info?id={0}&server=33'
|
||||||
|
|
||||||
def __init__(self, episode_id, quality='720p'):
|
def __init__(self, episode_id, quality='720p', callback=None):
|
||||||
|
|
||||||
if quality not in QUALITIES:
|
if quality not in QUALITIES:
|
||||||
raise AnimeDLError('Incorrect quality: "{}"'.format(quality))
|
raise AnimeDLError('Incorrect quality: "{}"'.format(quality))
|
||||||
|
|
||||||
self.episode_id = episode_id
|
self.episode_id = episode_id
|
||||||
self.quality = quality
|
self.quality = quality
|
||||||
|
self._callback = callback
|
||||||
|
if self._callback:
|
||||||
|
self._callback("Extracting stream info of id: {}".format(self.episode_id))
|
||||||
self.getData()
|
self.getData()
|
||||||
|
|
||||||
def getData(self):
|
def getData(self):
|
||||||
url = self._base_url.format(self.episode_id)
|
url = self._base_url.format(self.episode_id)
|
||||||
data = json.loads(requests.get(url).text)
|
data = json.loads(requests.get(url).text)
|
||||||
url = data.get('target')
|
url = data.get('target')
|
||||||
|
title_re = re.compile(r'"og:title" content="(.*)"')
|
||||||
|
image_re = re.compile(r'"og:image" content="(.*)"')
|
||||||
|
|
||||||
r = requests.get(url+'&q='+self.quality)
|
r = requests.get(url+'&q='+self.quality)
|
||||||
soup = BeautifulSoup(r.text, 'html.parser')
|
soup = BeautifulSoup(r.text, 'html.parser')
|
||||||
try:
|
try:
|
||||||
self.stream_url = soup.find_all('source')[0].get('src')
|
self.stream_url = soup.find_all('source')[0].get('src')
|
||||||
|
self.title = title_re.findall(r.text)[0]
|
||||||
|
self.image = image_re.findall(r.text)[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise NotFoundError("Episode not found")
|
raise NotFoundError("Episode not found")
|
||||||
|
|
||||||
def download():
|
def download(self):
|
||||||
pass
|
print('[INFO] Downloading {}'.format(self.title))
|
||||||
|
path = './' + self.title
|
||||||
|
r = requests.get(self.stream_url, stream=True)
|
||||||
|
|
||||||
|
total_size = r.headers['Content-length']
|
||||||
|
downloaded, chunksize = 0, 2048
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
if r.status_code == 200:
|
||||||
|
with open(path, 'wb') as f:
|
||||||
|
for chunk in r.iter_content(chunk_size=chunksize):
|
||||||
|
if chunk:
|
||||||
|
f.write(chunk)
|
||||||
|
downloaded += chunksize
|
||||||
|
write_status((downloaded), (total_size),
|
||||||
|
start_time)
|
||||||
|
|
||||||
|
|
||||||
|
def write_status(downloaded, total_size, start_time):
|
||||||
|
elapsed_time = time.time()-start_time
|
||||||
|
rate = (downloaded/1024)/elapsed_time
|
||||||
|
downloaded = float(downloaded)/1048576
|
||||||
|
total_size = float(total_size)/1048576
|
||||||
|
|
||||||
|
status = 'Downloaded: {0:.2f}MB/{1:.2f}MB, Rate: {2:.2f}KB/s'.format(
|
||||||
|
downloaded, total_size, rate)
|
||||||
|
|
||||||
|
sys.stdout.write("\r" + status + " "*5 + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import click
|
import click
|
||||||
|
import subprocess
|
||||||
from .anime import Anime, NotFoundError
|
from .anime import Anime, NotFoundError
|
||||||
|
|
||||||
echo = click.echo
|
echo = click.echo
|
||||||
|
@ -6,36 +7,52 @@ echo = click.echo
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.argument('anime_url')
|
@click.argument('anime_url')
|
||||||
@click.option('--range', help="Range of anime you want to"
|
@click.option('--range', 'range_', help="Range of anime you want to"
|
||||||
" download in the form <start>:<end>")
|
" download in the form <start>:<end>", metavar='<int>:<int>')
|
||||||
@click.option('--playlist', default=False, help="If falaf is set, saves the"
|
@click.option('--playlist', default=False, help="If flag is set, saves the"
|
||||||
" stream urls in an m3u file", type=bool, is_flag=True)
|
" stream urls in an m3u file", type=bool, is_flag=True)
|
||||||
@click.option('--url', default=False, help="If flag is set, prints the"
|
@click.option('--url', default=False, help="If flag is set, prints the"
|
||||||
" stream url and not download", type=bool, is_flag=True)
|
" stream url and not download", type=bool, is_flag=True)
|
||||||
def cli(anime_url, range, playlist, url):
|
@click.option('--play', 'player', metavar='PLAYER',
|
||||||
|
help="Streams in the specified player")
|
||||||
|
@click.option('--no-download', default=False, help="Retrieve without "
|
||||||
|
"downloading", is_flag=True)
|
||||||
|
@click.option('--quality', type=click.Choice(['360p', '480p', '720p']),
|
||||||
|
default='720p', help='Specify the quality of episode. Default-720p')
|
||||||
|
def cli(anime_url, range_, playlist, url, player, no_download, quality):
|
||||||
""" Anime Downloader
|
""" Anime Downloader
|
||||||
|
|
||||||
Download your favourite anime.
|
Download your favourite anime.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
anime = Anime(anime_url)
|
anime = Anime(anime_url, quality=quality,
|
||||||
|
callback=lambda message: print('[INFO] '+message))
|
||||||
except NotFoundError as e:
|
except NotFoundError as e:
|
||||||
echo(e.args[0])
|
echo(e.args[0])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if url or player:
|
||||||
|
no_download = True
|
||||||
|
|
||||||
if range is None:
|
if range is None:
|
||||||
range = '1:'+str(len(anime)+1)
|
range_ = '1:'+str(len(anime)+1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
start, end = [int(x) for x in range.split(':')]
|
start, end = [int(x) for x in range_.split(':')]
|
||||||
anime._episodeIds = anime._episodeIds[start-1:end-1]
|
anime._episodeIds = anime._episodeIds[start-1:end-1]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# Only one episode specified
|
# Only one episode specified
|
||||||
anime = [anime[int(range)-1]]
|
anime = [anime[int(range_)-1]]
|
||||||
|
|
||||||
for episode in anime:
|
for episode in anime:
|
||||||
if url:
|
if url:
|
||||||
print(episode.stream_url)
|
print(episode.stream_url)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
episode.download()
|
if player:
|
||||||
|
p = subprocess.Popen([player, episode.stream_url])
|
||||||
|
p.wait()
|
||||||
|
|
||||||
|
if not no_download:
|
||||||
|
episode.download()
|
||||||
|
print()
|
||||||
|
|
Loading…
Reference in New Issue