anime-downloader/anime_downloader/gui.py

485 lines
14 KiB
Python

import sys
import time
from PyQt5 import QtWidgets, QtGui, QtCore
from anime_downloader import session, util
from anime_downloader.commands import dl
from anime_downloader.config import Config
from anime_downloader.sites import get_anime_class, ALL_ANIME_SITES, exceptions
import os
import tempfile
import subprocess
class Worker(QtCore.QThread):
#Worker to run commands on another thread, allowing the GUI not to lock up. Theoretically, any function should be able to be passed to this Worker.
signal = QtCore.pyqtSignal(int)
def __init__(self, fn, *args, **kwargs):
QtCore.QThread.__init__(self)
self.fn = fn
self.args = args
self.kwargs = kwargs
@QtCore.pyqtSlot()
def run(self):
self.fn(*self.args, **self.kwargs, signal=self.signal)
class Window(QtWidgets.QMainWindow):
def __init__(self):
'''
Initialises the window as well as objects in the window which will always be constant regardless of pages.
'''
super().__init__()
self.updateProgress = None
self.defaultStyleSheet = self.setStyleSheet("")
self.setGeometry(50, 50, 400, 400)
self.setWindowTitle("Anime Downloader")
self.setWindowIcon(QtGui.QIcon('placeholder.png'))
self.statusBar()
menubar = self.menuBar()
themeMenu = menubar.addMenu('&Themes')
titles = ['Iggy', 'Arjix', 'Lagrad' ,'Red', 'Default']
methods = [self.__iggyTheme, self.__arjixTheme, self.__laggyTheme, self.__redTheme, self.__defaultTheme]
for x, y in zip(titles,methods):
theme = QtWidgets.QAction(f'&{x} Theme',self)
theme.triggered.connect(y)
themeMenu.addAction(theme)
self.downloadPage()
def downloadPage(self):
'''
Contains all the information for the main download page, this contains all the positioning inputs for the widgets used in this page, as well
as information regarding connecting signals to later functions.
'''
self.animeName = QtWidgets.QLineEdit()
self.animeEpisodeStart = QtWidgets.QLineEdit()
self.animeEpisodeEnd = QtWidgets.QLineEdit()
self.searchButton = QtWidgets.QPushButton('Search')
self.downloadDirectory = QtWidgets.QLineEdit()
self.file = QtWidgets.QPushButton('Browse')
self.providers = QtWidgets.QComboBox()
self.searchOutput = QtWidgets.QListWidget()
self.progressBar = QtWidgets.QProgressBar()
self.playPrompt = QtWidgets.QPushButton('Play')
self.downloadPrompt = QtWidgets.QPushButton('Download')
self.cancelButton = QtWidgets.QPushButton('Cancel')
#Allows for enter to be pressed to get search.
returnPressedWidgets = [self.animeName, self.animeEpisodeStart, self.animeEpisodeEnd]
for x in returnPressedWidgets:
x.returnPressed.connect(self.PrintResults)
self.PopulateProviders()
self.animeName.setPlaceholderText('Anime Name:')
self.animeEpisodeStart.setPlaceholderText('Anime Episode Start:')
self.animeEpisodeEnd.setPlaceholderText('Anime Episode End:')
self.downloadDirectory.setPlaceholderText('Download Directory:')
layout = QtWidgets.QVBoxLayout()
central_widget = QtWidgets.QWidget()
central_widget.setLayout(layout)
#Adds the widgets to the screen.
widgetNames = [self.animeName, self.animeEpisodeStart, self.animeEpisodeEnd, self.providers,
self.searchButton, self.searchOutput]
for x in widgetNames:
layout.addWidget(x)
horizontalLayout = QtWidgets.QHBoxLayout()
horizontalLayout.addWidget(self.downloadDirectory)
horizontalLayout.addWidget(self.file)
layout.addLayout(horizontalLayout)
horizontalLayout = QtWidgets.QHBoxLayout()
horizontalLayout.addWidget(self.downloadPrompt)
horizontalLayout.addWidget(self.playPrompt)
layout.addLayout(horizontalLayout)
layout.addWidget(self.progressBar)
layout.addWidget(self.cancelButton)
self.setCentralWidget(central_widget)
self.searchButton.clicked.connect(self.PrintResults)
self.file.clicked.connect(self.openFileDialog)
self.downloadPrompt.clicked.connect(self.download)
self.playPrompt.clicked.connect(self.play)
self.cancelButton.clicked.connect(self.cancel)
self.show()
def PrintResults(self):
'''
This function will return the search outputs for the user to select what anime they want.
'''
self.searchOutput.clear()
cls = get_anime_class(self.providers.currentText())
searchResults = cls.search(self.animeName.text())
searchResults = [v.title for v in searchResults]
self.searchOutput.addItems(searchResults)
self.searchOutput.repaint()
def openFileDialog(self):
'''
Opens the window selector for users to select what folder they want to download to.
'''
filename = QtWidgets.QFileDialog.getExistingDirectory()
self.downloadDirectory.setText(str(filename) + '/')
def PopulateProviders(self):
'''
Populates the drop down menu for all the providers we have available.
'''
sitenames = [v[1] for v in ALL_ANIME_SITES]
for site in sitenames:
self.providers.addItem(site)
def download(self):
'''
Contains all the information regarding updating the progress bar as well as passing the downloading to the Worker to download on another thread.
'''
self.downloadPrompt.setEnabled(False)
self.progressBar.setValue(0)
self.updateProgress = Worker(self.download_episodes)
self.updateProgress.signal.connect(self.onCountChanged)
self.updateProgress.start()
self.updateProgress.finished.connect(self.handleFinished)
def onCountChanged(self, value):
'''
Updates the progress bar.
'''
self.progressBar.setValue(value)
def play(self):
'''
Initialises the play button, passes the streaming to another thread.
'''
self.progressBar.setValue(0)
self.updateProgress = Worker(self.play_episodes)
self.updateProgress.signal.connect(self.onCountChanged)
self.updateProgress.start()
def play_episodes(self, signal):
'''
Brings all the episodes into a m3u8 playlist for one continuous mpv.
'''
self.signal = signal
animes, anime_title = self.get_animes()
self.progressBar.setMaximum(len(animes._episode_urls))
file = self.generate_m3u8(animes)
p = subprocess.Popen([Config["gui"]["player"], file])
p.wait()
def get_animes(self):
# if nothing is selected it returns -1
# this makes the choice the first one if nothing is selected from search.
if self.searchOutput.currentRow() != -1:
choice = self.searchOutput.currentRow() + 1
else:
choice = 1
start = self.animeEpisodeStart.text(
) if self.animeEpisodeStart.text().isnumeric() else 1
end = int(self.animeEpisodeEnd.text()) + \
1 if self.animeEpisodeEnd.text().isnumeric() else ''
episode_range = f'{start}:{end}'
anime = self.animeName.text()
provider = self.providers.currentText()
print(anime, provider, choice)
anime_url, _ = util.search(anime, provider, choice)
cls = get_anime_class(anime_url)
anime = cls(anime_url)
ep_range = util.parse_episode_range(len(anime), episode_range)
animes = util.split_anime(anime, ep_range)
# animes = util.parse_ep_str(anime, episode_range)
anime_title = anime.title
# maybe make animes/anime_title self.animes?
return animes, anime_title
def get_download_dir(self):
# Reads the input download dir and if it's empty it uses default.
download_dir = self.downloadDirectory.text()
if not download_dir:
download_dir = Config["dl"]["download_dir"]
download_dir = os.path.abspath(download_dir)
return download_dir
def generate_m3u8(self, animes):
filepath = tempfile.gettempdir() + '/MirrorList.m3u8'
text = "#EXTM3U\n"
for count, episode in enumerate(animes, 1):
print(count)
text += f"#EXTINF:,Episode {(episode.ep_no)}\n"
text += episode.source().stream_url + "\n"
self.signal.emit(count)
with open(filepath, "w") as f:
f.write(text)
return filepath
def download_episodes(self, signal):
self.signal = signal
animes, anime_title = self.get_animes()
self.progressBar.setMaximum(len(animes._episode_urls))
download_dir = self.get_download_dir()
for count, episode in enumerate(animes, 1):
ep_no = episode.ep_no
external = Config["dl"]["external_downloader"]
if external:
util.external_download(
Config["dl"]["external_downloader"],
episode,
Config["dl"]["file_format"],
Config["dl"]["speed_limit"],
path=download_dir
)
else:
episode.download(force=Config["dl"]["force_download"],
path=download_dir,
format=Config["dl"]["file_format"],
range_size=Config["dl"]["chunk_size"]
)
self.signal.emit(count)
time.sleep(1)
def cancel(self):
if self.updateProgress:
self.progressBar.setValue(0)
self.updateProgress.exit()
print("Process has ended")
self.updateProgress = None
else:
return None
def handleFinished(self):
self.downloadPrompt.setEnabled(True)
def __iggyTheme(self):
self.setStyleSheet("""
QMainWindow,
QAbstractItemView,
QTabBar::tab
{
color: #90EE90;
background: #000000;
}
QPushButton {
color: #90EE90;
background-color: #e0558b;
border-style: double;
border-color: #323C72;
border-radius: 2px;
padding: 5px;
}
QPushButton:hover {
background-color: #a88ca2;
}
QPushButton:pressed {
background-color: #838FD0;
}
QLineEdit {
background: #8B008B;
background-color: #8B008B;
border-radius: 2px;
padding: 5px;
color: #90EE90;
}
QComboBox {
background: #8B008B;
background-color: #8B008B;
color: #90EE90;
}
QProgressBar{
color: #90EE90;
}
*/
""")
def __laggyTheme(self):
self.setStyleSheet("""
QMainWindow,
QAbstractItemView,
QTabBar::tab
{
color: white;
background: #1a2035;
}
QPushButton {
color: white;
background-color: #5465bf;
border-style: double;
border-color: #323C72;
border-radius: 2px;
padding: 5px;
}
QPushButton:hover {
background-color: #6574C5;
}
QPushButton:pressed {
background-color: #838FD0;
}
QLineEdit {
background: #5A628E;
color: white;
}
QLineEdit:hover {
background: #6A7199;
}
QComboBox {
color: white;
background: #5465bf;
border: 1px solid #323C72;
border-radius: 3px;
padding: 4px 5px;
}
QComboBox:hover {
background-color: #6574C5;
}
QProgressBar {
text-align: center;
color: black;
border: 2px solid #6574C5;
border-radius: 3px;
background: #5A628E;
}
QProgressBar::chunk {
background-color: white;
width: 20px;
}
*/
""")
def __redTheme(self):
self.setStyleSheet("""
QMainWindow,
QAbstractItemView,
QTabBar::tab
{
color: #FF0000;
background: #000000;
}
QPushButton {
color: #BB0000;
background-color: #000000;
border-style: double;
border-color: #BB0000;
border-radius: 2px;
padding: 5px;
}
QPushButton:hover {
background-color: #BB0000;
}
QPushButton:pressed {
background-color: #BB0000;
}
QLineEdit {
background: #000000;
border-color: #BB0000;
color: #FF0000;
}
QLineEdit:hover {
background: #000000;
}
QComboBox {
color: #FF0000;
background: #000000;
border-color: #BB0000;
border: 1px solid #323C72;
border-radius: 3px;
padding: 4px 5px;
}
QComboBox:hover {
background-color: #650000;
border-color: #BB0000;
}
QProgressBar {
text-align: center;
color: black;
border-color: #BB0000;
border: 2px solid #650000;
border-radius: 3px;
background: #000000;
}
QProgressBar::chunk {
background-color: #000000;
width: 20px;
}
*/
""")
def __arjixTheme(self):
self.setStyleSheet("""
QMainWindow,
QAbstractItemView,
QTabBar::tab
{
color: #697041;
background: #3a192e;
}
QPushButton {
color: #0e1a63;
background-color: #88d353;
border-style: double;
border-color: #320000;
border-radius: 2px;
padding: 5px;
}
QPushButton:hover {
background-color: #88d353;
}
QPushButton:pressed {
background-color: #88d353;
}
QLineEdit {
background: #88d353;
color: #0e1a63;
}
QLineEdit:hover {
background: #88d353;
}
QComboBox {
color: #0e1a63;
background: #88d353;
border: 1px solid #323C72;
border-radius: 3px;
padding: 4px 5px;
}
QComboBox:hover {
background-color: #88d353;
}
QProgressBar {
text-align: center;
color: #88d353;
border: 2px solid #0e1a63;
border-radius: 3px;
background: #88d353;
}
QProgressBar::chunk {
background-color: #88d353;
width: 20px;
}
*/
""")
def __defaultTheme(self):
self.setStyleSheet("")
application = QtWidgets.QApplication(sys.argv)
GUI = Window()
sys.exit(application.exec_())