anime-downloader/anime_downloader/anime.py

124 lines
3.6 KiB
Python

import requests
from bs4 import BeautifulSoup
import json
import re
import time
import sys
QUALITIES = ['360p', '480p', '720p']
class AnimeDLError(Exception):
pass
class URLError(AnimeDLError):
pass
class NotFoundError(AnimeDLError):
pass
class Anime:
def __init__(self, url, quality='720p', callback=None):
if quality not in QUALITIES:
raise AnimeDLError('Incorrect quality: "{}"'.format(quality))
self.url = url
self.quality = quality
self._callback = callback
if self._callback:
self._callback('Extracting episode info from page')
self.getEpisodes()
def getEpisodes(self):
self._episodeIds = []
r = requests.get(self.url)
soup = BeautifulSoup(r.text, 'html.parser')
episodes = soup.find_all('ul', ['episodes'])
if episodes == []:
err = 'No episodes found in url "{}"'.format(self.url)
if self._callback:
self._callback(err)
args = [self.url]
raise NotFoundError(err, *args)
episodes = episodes[:int(len(episodes)/3)]
for x in episodes:
for a in x.find_all('a'):
ep_id = a.get('data-id')
self._episodeIds.append(ep_id)
def __len__(self):
return len(self._episodeIds)
def __getitem__(self, index):
ep_id = self._episodeIds[index]
return Episode(ep_id, self.quality, callback=self._callback)
class Episode:
_base_url = r'https://9anime.is/ajax/episode/info?id={0}&server=33'
def __init__(self, episode_id, quality='720p', callback=None):
if quality not in QUALITIES:
raise AnimeDLError('Incorrect quality: "{}"'.format(quality))
self.episode_id = episode_id
self.quality = quality
self._callback = callback
if self._callback:
self._callback("Extracting stream info of id: {}".format(self.episode_id))
self.getData()
def getData(self):
url = self._base_url.format(self.episode_id)
data = json.loads(requests.get(url).text)
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)
soup = BeautifulSoup(r.text, 'html.parser')
try:
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:
raise NotFoundError("Episode not found")
def download(self):
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()