485 lines
14 KiB
Python
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_())
|