PyQt GUI (#526)
* Add files via upload * Update setup.py * added gui command * Who messed with this? * PEP - fix * fixes for gui * Update gui.py * Update gui.py * Update gui.py * Update gui.py * Update gui.py * bugfix for search * Added Default, Lagrad and Iggy theme At the moment in the code until we get other people's theme to use. * placeholder image in here until we officially get one * Fix default player (#5) * Update config.py * Update gui.py * updated laggy theme * Red theme (#6) * Red theme This needs testing * Update gui.py * Update gui.py * Update gui.py (#7) * added cancel button * Update gui.py * No more downloading when downloading * Move pyqt dependency and autopep8 (#8) * move pyQt to extras_require * This was Blatzar's job😭😭😭😭 * Nicer generation of the themes courtesy of Red * Some more nicer stuff * Update gui.py Co-authored-by: Arjix <53124886+ArjixGamer@users.noreply.github.com> Co-authored-by: Blatzar <blatzar@gmail.com> Co-authored-by: Blatzar <46196380+Blatzar@users.noreply.github.com> Co-authored-by: AbdullahM0hamed <25087116+AbdullahM0hamed@users.noreply.github.com>master
parent
64e9313ef9
commit
1e8b01338b
|
@ -0,0 +1,6 @@
|
|||
import click
|
||||
|
||||
|
||||
@click.command()
|
||||
def command():
|
||||
import anime_downloader.gui
|
|
@ -40,6 +40,9 @@ DEFAULT_CONFIG = {
|
|||
'provider': 'twist.moe',
|
||||
'autoplay_next': True
|
||||
},
|
||||
'gui': {
|
||||
'player': 'mpv'
|
||||
},
|
||||
'siteconfig': {
|
||||
'animefrenzy': {
|
||||
'version': 'subbed'
|
||||
|
@ -267,7 +270,8 @@ class _Config:
|
|||
try:
|
||||
conf = json.load(configfile)
|
||||
except:
|
||||
raise SyntaxWarning('The config file is not correctly formatted')
|
||||
raise SyntaxWarning(
|
||||
'The config file is not correctly formatted')
|
||||
return conf
|
||||
|
||||
def _write_default_config(self):
|
||||
|
|
|
@ -0,0 +1,484 @@
|
|||
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_())
|
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
Loading…
Reference in New Issue