diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d85d89c --- /dev/null +++ b/.gitignore @@ -0,0 +1,141 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# Executable files +exe/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..622b02e --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +
+ BitAnime +

BitAnime

+

+ A Python script that allows you to download all of an anime's episodes at once. +

+ · Download executable version · +
+ +## About BitAnime + +**BitAnime** is a python script that allows you to download anime in large batches. It can also be used to download anime films. **BitAnime** gets its content from [gogoanime](https://gogoanime.pe/). If you get a **404** error, please look up the correct anime name on [gogoanime](https://gogoanime.pe/). This application can only download **1**-**99** episodes at the time. At the moment, the quality of the episodes that will be downloaded is different for every anime. For **older anime**, the quality will be **360p** to **480p**, for **newer anime** the quality will be **720p** to **1080p**. + +## Installation + +```console +git clone https://github.com/sh1nobuu/BitAnime.git +``` + +## Screenshot + +
+ BitAnime Screenshot +
+ +## Dependencies + +**BitAnime** is highly reliant on the python modules `requests`, `colorama`, `tqdm`, and `BeautifulSoup`. + +```console +pip install -r requirements.txt +``` + +## Usage + +The anime name is separated by "-". You can either type it manually, or go to [gogoanime.pe](https://gogoanime.pe/) and search for the anime you want to download and copy the name from the URL. + +### Examples + +##### One word title + +- https://gogoanime.pe/category/bakemonogatari >> bakemonogatari +- https://gogoanime.pe/category/steinsgate >> steinsgate + +##### Multiple word title + +- https://gogoanime.pe/category/shadows-house >> shadows-house +- https://gogoanime.pe/category/kono-subarashii-sekai-ni-shukufuku-wo- >> kono-subarashii-sekai-ni-shukufuku-wo- diff --git a/images/ba-logo.png b/images/ba-logo.png new file mode 100644 index 0000000..cb8d0e6 Binary files /dev/null and b/images/ba-logo.png differ diff --git a/images/ba-screenshot.PNG b/images/ba-screenshot.PNG new file mode 100644 index 0000000..ec13c2b Binary files /dev/null and b/images/ba-screenshot.PNG differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..079fb68 Binary files /dev/null and b/requirements.txt differ diff --git a/src/backend.py b/src/backend.py new file mode 100644 index 0000000..5ac4bfa --- /dev/null +++ b/src/backend.py @@ -0,0 +1,68 @@ +import requests +import os +import shutil +from bs4 import BeautifulSoup + +folder_path = "" + + +def get_path(folder): + global folder_path + folder_path = folder + + +def get_links(name, episode_number, source=None): + if source is not None: + source_ep = f"https://gogoanime.pe/{name}-episode-" + episode_links = [f"{source_ep}{i}" for i in range(1, int(episode_number) + 1)] + episode_links.insert(0, source) + else: + source_ep = f"https://gogoanime.pe/{name}-episode-" + episode_links = [f"{source_ep}{i}" for i in range(1, int(episode_number) + 1)] + return episode_links + + +def get_download_links(episode_links): + download_links = [] + for episode_link in episode_links: + episode_link_resp = requests.get(episode_link) + soup = BeautifulSoup(episode_link_resp.content, "html.parser") + links = soup.find("li", {"class": "dowloads"}) + for link in links: + link = link.get("href") + download_links.append(link) + return download_links + + +def get_download_urls(download_links, bool): + download_urls = [] + for link in download_links: + link = requests.get(link) + soup = BeautifulSoup(link.content, "html.parser") + download_link = soup.find_all("div", {"class": "dowload"}) + download_urls.append(download_link[0].a.get("href")) + if bool: + conv_download_urls = { + episode_title: url for episode_title, url in enumerate(download_urls) + } + else: + conv_download_urls = { + episode_title + 1: url + for episode_title, url in enumerate(download_urls) + } + conv_download_urls = sorted(set(conv_download_urls.items())) + return conv_download_urls + + +def download_episodes(url): + header = { + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip, deflate", + "Connection": "close", + } + url_resp = requests.get(url[1], headers=header, stream=True) + file_name = os.path.join(folder_path, f"{url[0]}.mp4") + with open(file_name, "wb") as file: + shutil.copyfileobj(url_resp.raw, file) diff --git a/src/bitanime.py b/src/bitanime.py new file mode 100644 index 0000000..998da4d --- /dev/null +++ b/src/bitanime.py @@ -0,0 +1,114 @@ +# Dependencies + +import requests +import ctypes +import os +import backend as bd +import colorama +from tqdm.contrib.concurrent import thread_map +from bs4 import BeautifulSoup +from colorama import Fore + +colorama.init(autoreset=True) +ctypes.windll.kernel32.SetConsoleTitleW("BitAnime") + + +def bitanime(): + again = True + while again: + print( + f""" {Fore.LIGHTBLUE_EX} + ____ _ _ _ _ + | __ )(_) |_ / \ _ __ (_)_ __ ___ ___ + | _ \| | __| / _ \ | '_ \| | '_ ` _ \ / _ \\ + | |_) | | |_ / ___ \| | | | | | | | | | __/ + |____/|_|\__/_/ \_\_| |_|_|_| |_| |_|\___| + {Fore.LIGHTYELLOW_EX} + By: sh1nobu + Github: https://github.com/sh1nobuu/BitAnime + """ + ) + """ + Ask user for input and then check if the anime provided exists or if not, loop + """ + check = True + while check: + name = input(f"Enter anime name >> ").lower() + if "-" in name: + title = name.replace("-", " ").title().strip() + else: + title = name.title().strip() + source = f"https://gogoanime.pe/category/{name}" + resp = requests.get(source) + if resp.status_code == 200: + print(f"{Fore.LIGHTGREEN_EX}====================================") + check = False + else: + print( + f"{Fore.LIGHTRED_EX}Error 404: Anime not found. Please try again." + ) + check = True + """ + Get how many episode/s the anime has + """ + soup = BeautifulSoup(resp.content, "html.parser") + episode_number = soup.find("ul", {"id": "episode_page"}) + episode_number = episode_number.get_text().split("-")[1].strip() + """ + Print the anime name, episode, and the link of the anime + """ + print(f"Title: {Fore.LIGHTCYAN_EX}{title}") + print(f"Episode/s: {Fore.LIGHTCYAN_EX}{episode_number}") + print(f"Link: {Fore.LIGHTCYAN_EX}{source}") + print(f"{Fore.LIGHTGREEN_EX}====================================") + """ + Create a download folder for the anime + """ + folder = os.path.join(os.getcwd(), title) + if not os.path.exists(folder): + os.mkdir(folder) + """ + Check if the anime has episode 0 or not + """ + source = f"https://gogoanime.pe/{name}" + resp = requests.get(source) + soup = BeautifulSoup(resp.content, "html.parser") + episode_zero = soup.find("h1", {"class": "entry-title"}) + if episode_zero is None: + # Episode 0 does exist + episode_links = bd.get_links(name, episode_number, source) + download_links = bd.get_download_links(episode_links) + download_urls = bd.get_download_urls(download_links, True) + print(f"Downloading {Fore.LIGHTCYAN_EX}{len(download_urls)} episode/s") + print(f"{Fore.LIGHTGREEN_EX}====================================") + print(download_urls) + bd.get_path(folder) + thread_map( + bd.download_episodes, download_urls, ncols=75, total=len(download_urls) + ) + os.startfile(folder) + + else: + # Episode 0 does not exist + episode_links = bd.get_links(name, episode_number) + download_links = bd.get_download_links(episode_links) + download_urls = bd.get_download_urls(download_links, False) + print( + f"Downloading {Fore.LIGHTCYAN_EX}{len(download_urls)}{Fore.RESET} episode/s" + ) + print(f"{Fore.LIGHTGREEN_EX}====================================") + bd.get_path(folder) + thread_map( + bd.download_episodes, download_urls, ncols=75, total=len(download_urls) + ) + os.startfile(folder) + use_again = input("Do you want to download other anime? (y|n) >> ").lower() + if use_again == "y": + again = True + os.system("cls") + else: + again = False + + +if __name__ == "__main__": + bitanime()