Skip to content

Commit

Permalink
Merge pull request #5 from Oshan96/dev
Browse files Browse the repository at this point in the history
Dev - v1.0.0
  • Loading branch information
Oshan96 authored Mar 25, 2020
2 parents 37f194b + d12be25 commit 20d86bb
Show file tree
Hide file tree
Showing 24 changed files with 1,222 additions and 328 deletions.
35 changes: 28 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
# Anime Downloader [![Total Downloads](https://img.shields.io/github/downloads/Oshan96/Anime-Downloader/total.svg?style=for-the-badge)](https://github.com/Oshan96/Anime-Downloader/releases)

There are two scripts (Anime_Downloader.py, Anime_Scraper.py) to download given anime to a directory and to extract direct download links.
Anime_Scraper.py scraper is used to collect and extract direct anime download links from 9anime.to (From its Mp4Upload server)
You can now bulk download your favourite anime episodes for various websites, in various resolutions, with or without filler episodes

[See supported websites](#Supported-Websites)

## Donations
If this project is helpful to you and love my work and feel like showing love/appreciation, would you like to buy me a coffee?
<a href="https://www.buymeacoffee.com/Oshan96" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>

## Supported Websites
| Website | Need recaptcha token? | Supported resolutions | FFMPEG needed? |
|--- |--- |--- |--- |
| [9Anime](https://9anime.to/) | Yes | Default only | No |
| [4Anime](https://4anime.to/) | No | Default only | No |
| [AnimePahe](https://animepahe.com/) | No | 720p, 1080p | No |
| [AnimeUltima](https://www.animeultima.to/) | No | 240p, 360p, 480p, 720p, 1080p | Yes |

## Download Anime Downloader [Windows]
> Note : Currently only windows executable is provided (Linux, Mac users go to [Build from source](#Building-from-source))
Download the [Latest Relase](https://github.com/Oshan96/Anime-Downloader/releases) from here and extract the zip file

## Downloading
## Downloading Your Favourite Anime

First of all, Anime Downloader uses [2captcha](https://www.2captcha.com) to bypass google recaptcha, so you need to purchase one
First of all for websites which require capatcha token, Anime Downloader uses [2captcha](https://www.2captcha.com) to bypass google recaptcha, so you need to purchase one ([Check whether your anime website needs captcha token](#Supported-Websites))

Open settings.json and set [2captcha](https://2captcha.com/) API key in "api_key"

Expand All @@ -24,13 +33,25 @@ Open settings.json and set [2captcha](https://2captcha.com/) API key in "api_key

*Don't have 2captcha API key? Don't worry! You can still use this to download anime. Check the "FAQ" section on [how to download if you don't have a 2captcha API key](#Q---I-don't-have-a-2captcha-API-key,-is-there-any-workaround-for-that?)*

##### And in order to download from some websites (like animeultima.to) Anime Downloader requires you to have [FFMPEG](https://www.ffmpeg.org/) to be downloaded ([Check whether your anime website needs FFMPEG](#Supported-Websites))

- You can download FFMPEG from [here](https://www.ffmpeg.org/download.html)
- And then add the ffmpeg executable to system path
Or, in your linux environment,
```bash
sudo apt install ffmpeg
```

#### Download failed and weird error came? Don't worry, it's because these websites are protected by various security measures. Simply, just visit the website manually, and restart the anime downloader!

#### Still not able to download? Go ahead and post your issue [here](https://github.com/Oshan96/Anime-Downloader/issues). And I will look into the error and give necessary fixes!

## Running the application
Navigate to the extracted folder and open a cmd or powershell window from that folder and execute "anime-dl.exe" from command line.

## How to download using GUI version (v0.1.1-alpha upwards)
It is same as the CLI version, but provided a graphical user interface to collect necessary parameters.

> Note : The GUI version is still in development and this is a pre-release. The code and execution methods will probably change in future
Execute the "anime-dl.exe" to start.

If you're running from source files, execute the "anime-dl.py" script
Expand All @@ -43,7 +64,7 @@ And the GUI will appear as following :

![GUI](docs/images/gui.png)

#### Note : If you don't have a 2captcha API key, you need to [provide "Recaptcha Token" in the given text field](#Q---I-don't-have-a-2captcha-API-key,-is-there-any-workaround-for-that?) (check FAQ section)
#### Note : If you don't have a 2captcha API key, you need to [provide "Recaptcha Token" in the given text field](#Q---I-don't-have-a-2captcha-API-key,-is-there-any-workaround-for-that?) for websites require captcha token (check FAQ section)

## How to download using anime-dl (CLI)?

Expand Down
199 changes: 110 additions & 89 deletions anime_downloader/Anime_Downloader.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from scrapers.nineanime import Anime_Scraper
from util import Color
import warnings
import ssl
import argparse
Expand All @@ -8,160 +6,183 @@
import os
import sys
from platform import system

from threading import Thread
from queue import Queue
from art import text2art
from util import Color
from util.ffmpeg_downloader import FFMPEGDownloader
from scrapers.nineanime import Anime_Scraper

directory = ""
threads = 1

token = None

titles = False

args = None

gui = None

class Worker(Thread) :
def __init__(self, tasks) :

class Worker(Thread):
def __init__(self, tasks, gui=None):
Thread.__init__(self)
self.tasks = tasks
self.gui = gui
self.daemon = True
self.start()

def run(self) :
global gui
while True :

def run(self):
while True:
func, arg, kargs = self.tasks.get()
try :
try:
func(*arg, **kargs)
except Exception as ex :
# print(ex)
Color.printer("ERROR", ex, gui)
finally :
except Exception as ex:
Color.printer("ERROR", ex, self.gui)
finally:
self.tasks.task_done()

class ThreadPool :
def __init__(self, num_threads) :

class ThreadPool:
def __init__(self, num_threads, gui=None):
self.tasks = Queue(num_threads)
for _ in range(num_threads) :
Worker(self.tasks)
def add_task(self, func, *arg, **kargs) :
for _ in range(num_threads):
Worker(self.tasks, gui)

def add_task(self, func, *arg, **kargs):
self.tasks.put((func, arg, kargs))
def map(self, func, args_list) :
for arg in args_list :

def map(self, func, args_list):
for arg in args_list:
self.add_task(func, arg)
def wait_completion(self) :

def wait_completion(self):
self.tasks.join()


def clean_file_name(file_name) :
for c in r'[]/\;,><&*:%=+@#^()|?^':
file_name = file_name.replace(c,'')

return file_name
class Downloader:
def __init__(self, directory, episodes, threads=1, gui=None, is_titles=False):
self.directory = directory
self.threads = threads
self.episodes = episodes
self.is_titles = is_titles
self.gui = gui

def __clean_file_name(self, file_name):
for c in r'[]/\;,><&*:%=+@#^()|?^':
file_name = file_name.replace(c, '')

return file_name

def __download_episode(self, episode):
if episode.is_direct:
if episode.download_url is None:
Color.printer("ERROR", "Download URL is not set for " + episode.episode + ", skipping...", self.gui)
return

def download_episode(episode) :
global titles, gui
Color.printer("INFO", "Downloading " + episode.episode + "...", self.gui)

Color.printer("INFO", "Downloading " + episode.episode + "...", gui)
if system() == "Windows":
episode.title = self.__clean_file_name(episode.title)

if system() == "Windows" :
episode.title = clean_file_name(episode.title)
# print(self.is_titles)
# print(episode.title)

if titles :
file_name = directory + episode.episode + " - " + episode.title + ".mp4"
else :
file_name = directory+episode.episode+".mp4"
if self.is_titles:
# print("with title")
file_name = self.directory + episode.episode + " - " + episode.title + ".mp4"
else:
# print("without title")
file_name = self.directory + episode.episode + ".mp4"

with requests.get(episode.download_url, stream=True, verify=False) as r:
with open(file_name, 'wb') as f:
shutil.copyfileobj(r.raw, f, length=16*1024*1024)
with requests.get(episode.download_url, stream=True, verify=False) as r:
with open(file_name, 'wb') as f:
shutil.copyfileobj(r.raw, f, length=16 * 1024 * 1024)

Color.printer("INFO", episode.episode + " finished downloading...", gui)
Color.printer("INFO", episode.episode + " finished downloading...", self.gui)

else:
Color.printer("INFO", "HLS link found. Using FFMPEG to download...", self.gui)
FFMPEGDownloader(episode, self.directory, self.gui).download()

def download() :
global directory, threads, gui
def download(self):

try:
_create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
# Legacy Python that doesn't verify HTTPS certificates by default
pass
else:
# Handle target environment that doesn't support HTTPS verification
ssl._create_default_https_context = _create_unverified_https_context
try:
_create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
# Legacy Python that doesn't verify HTTPS certificates by default
pass
else:
# Handle target environment that doesn't support HTTPS verification
ssl._create_default_https_context = _create_unverified_https_context

Color.printer("INFO", "Downloading started...", gui)
Color.printer("INFO", "Downloading started...", self.gui)

# for episode in Anime_Scraper.episodes :
# print("Downloading", episode.episode)
# urllib.request.urlretrieve(episode.download_url, directory+episode.episode+".mp4")

pool = ThreadPool(threads)
pool = ThreadPool(self.threads, gui)

pool.map(download_episode, Anime_Scraper.episodes)
pool.wait_completion()
pool.map(self.__download_episode, self.episodes)
pool.wait_completion()

Color.printer("INFO", "Downloading finished!", gui)
Color.printer("INFO", "Downloading finished!", self.gui)


def print_banner() :
def print_banner():
banner = text2art("Anime Downloader")
Color.printer("BANNER", banner)


def main() :
def main():
global directory, args, threads, titles, token

print_banner()

parser = argparse.ArgumentParser(description="Anime Downloader Command Line Tool")
argparse.ArgumentParser(description="Help option parcer for Anime Downloader Command Line Tool", add_help=False, formatter_class=argparse.HelpFormatter)
argparse.ArgumentParser(description="Help option parcer for Anime Downloader Command Line Tool", add_help=False,
formatter_class=argparse.HelpFormatter)

parser.add_argument("-u", "--url", required=True, help="9Anime.to URL for the anime to be downloaded", dest="url")
parser.add_argument("-n", "--names", required=True, help="https://www.animefillerlist.com/ URL to retrieve episode titles", dest="title_url")
parser.add_argument("-d", "--directory", required=False, help="Download destination. Will use the current directory if not provided", default="" , dest="dir")
parser.add_argument("-s", "--start", required=False, help="Starting episode",default=1, type=int , dest="start")
parser.add_argument("-e", "--end", required=False, help="End episode", default=9999, type=int ,dest="end")
parser.add_argument("-c", "--code", required=False, help="Recaptcha answer token code. Insert this if you don't have 2captcha captcha bypass api_key", default=None, dest="token")
parser.add_argument("-t", "--threads", required=False, help="Number of parrallel downloads. Will download sequencially if not provided", default=1, type=int ,dest="threads")
parser.add_argument("-f", "--filler", required=False, help="Whether fillers needed", default=True, type=bool ,dest="isFiller")
parser.add_argument("-n", "--names", required=True,
help="https://www.animefillerlist.com/ URL to retrieve episode titles", dest="title_url")
parser.add_argument("-d", "--directory", required=False,
help="Download destination. Will use the current directory if not provided", default="",
dest="dir")
parser.add_argument("-s", "--start", required=False, help="Starting episode", default=1, type=int, dest="start")
parser.add_argument("-e", "--end", required=False, help="End episode", default=9999, type=int, dest="end")
parser.add_argument("-c", "--code", required=False,
help="Recaptcha answer token code. Insert this if you don't have 2captcha captcha bypass api_key",
default=None, dest="token")
parser.add_argument("-t", "--threads", required=False,
help="Number of parrallel downloads. Will download sequencially if not provided", default=1,
type=int, dest="threads")
parser.add_argument("-f", "--filler", required=False, help="Whether fillers needed", default=True, type=bool,
dest="isFiller")

args = parser.parse_args()

Anime_Scraper.download_9anime_url = args.url
Anime_Scraper.title_url = args.title_url
Anime_Scraper.isFiller = args.isFiller
# Anime_Scraper.ts_no = args.ts_no

token = args.token
directory = args.dir
threads = args.threads

if args.title_url :
if args.title_url:
titles = True

if directory != "" :
if directory != "":
directory = directory.replace("\\", "/")
if not directory.endswith("/") :
directory+="/"
if not directory.endswith("/"):
directory += "/"

Anime_Scraper.main(args.start, args.end, token)

download()
Downloader(directory, Anime_Scraper.episodes, threads, gui, titles).download()


if __name__ == "__main__":
#suppress warnings
# suppress warnings
warnings.filterwarnings("ignore")
#activate color codes
if sys.platform.lower() == "win32" :
os.system("color")

# activate color codes
if sys.platform.lower() == "win32":
os.system("color")

main()
14 changes: 9 additions & 5 deletions anime_downloader/anime-dl.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import Anime_Downloader
from scrapers.nineanime import Anime_Scraper
import os
import sys
import warnings
from queue import Queue
from gui.GUI import Anime_GUI
from gui.GUI import AnimeGUI

if __name__ == "__main__" :
if __name__ == "__main__":
warnings.filterwarnings("ignore")
Anime_GUI(Queue(), Anime_Downloader, Anime_Scraper).run()
# activate color codes
if sys.platform.lower() == "win32":
os.system("color")

AnimeGUI(Queue()).run()
Empty file.
15 changes: 15 additions & 0 deletions anime_downloader/extractors/base_extractor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class BaseExtractor:

def __init__(self, url, session):
self.url = url
self.session = session

def extract_page_content(self):
video_page = self.session.get(self.url).content
return video_page.decode('utf-8')

def extract_direct_url(self):
raise NotImplementedError

def direct_link(self):
return self.extract_direct_url()
Loading

0 comments on commit 20d86bb

Please sign in to comment.