* 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
Iggy 2020-10-16 23:25:50 +01:00 committed by GitHub
parent 64e9313ef9
commit 1e8b01338b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 496 additions and 1 deletions

View File

@ -0,0 +1,6 @@
import click
@click.command()
def command():
import anime_downloader.gui

View File

@ -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):

484
anime_downloader/gui.py Normal file
View File

@ -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

View File

@ -34,6 +34,7 @@ setup(
],
extras_require={
'selescrape': ['selenium'],
'gui': ['PyQt5>=5.15.1', 'selenium'],
'dev': [
'pytest',
'httpretty'