Compare commits
5 Commits
1e7d9db9cc
...
6ace97ff83
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ace97ff83 | |||
| 9234da67fe | |||
| 3227730a37 | |||
| afb7a456a1 | |||
| a38221cca2 |
+123
-50
@@ -1,30 +1,54 @@
|
||||
from typing import Type
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from urllib import parse
|
||||
import urllib.parse
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtQml import qmlRegisterType, QQmlApplicationEngine
|
||||
from PyQt5.QtQuick import QQuickImageProvider
|
||||
|
||||
# from PyQt5.QtQuick import QQuickView
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSlot
|
||||
|
||||
from PyQt5.QtGui import QImage
|
||||
|
||||
# from PyQt5.QtCore import QObject, QUrl, pyqtProperty
|
||||
|
||||
|
||||
from .qtmpv import MpvObject
|
||||
|
||||
import requests
|
||||
import toml
|
||||
|
||||
|
||||
class Show(QObject):
|
||||
class Episode(QObject):
|
||||
def __init__(self, source, parent=None):
|
||||
super().__init__(parent)
|
||||
self._source = source
|
||||
|
||||
@pyqtProperty("QString", constant=True)
|
||||
def title(self):
|
||||
return self._source["Title"]
|
||||
|
||||
@pyqtProperty(int, constant=True)
|
||||
def season(self):
|
||||
return self._source["Season"]
|
||||
|
||||
@pyqtProperty(int, constant=True)
|
||||
def episode(self):
|
||||
return self._source["Episode"]
|
||||
|
||||
@pyqtProperty("QString", constant=True)
|
||||
def filename(self):
|
||||
return self._source["OriginalFilename"]
|
||||
|
||||
|
||||
class Show(QObject):
|
||||
def __init__(self, source, episodes, parent=None):
|
||||
super().__init__(parent)
|
||||
self._source = source
|
||||
self._episodes = episodes
|
||||
|
||||
@pyqtProperty("QString", constant=True)
|
||||
def title(self) -> str:
|
||||
return self._source["title"]
|
||||
@@ -37,10 +61,6 @@ class Show(QObject):
|
||||
def description(self) -> str:
|
||||
return self._source["description"]
|
||||
|
||||
@pyqtProperty(int, constant=True)
|
||||
def episodes(self) -> int:
|
||||
return self._source["episodes"]
|
||||
|
||||
@pyqtProperty(int, constant=True)
|
||||
def watched(self) -> int:
|
||||
return self._source["watched"]
|
||||
@@ -49,6 +69,10 @@ class Show(QObject):
|
||||
def poster(self) -> str:
|
||||
return self._source["poster"]
|
||||
|
||||
@pyqtProperty(list, constant=True)
|
||||
def episodes(self) -> list[Episode]:
|
||||
return self._episodes
|
||||
|
||||
|
||||
class ProviderImageProvider(QQuickImageProvider):
|
||||
def __init__(self, icon_data):
|
||||
@@ -60,26 +84,21 @@ class ProviderImageProvider(QQuickImageProvider):
|
||||
print(p_str)
|
||||
print(size)
|
||||
|
||||
# img = QImage.fromData(self._icon_data.encode("utf-8"))
|
||||
import base64
|
||||
|
||||
data: bytes = base64.b64decode(self._icon_data)
|
||||
img = QImage.fromData(data)
|
||||
# img = QImage.fromData(self._icon_data.encode("utf-8"))
|
||||
|
||||
# img = QImage(300, 300, QImage.Format_RGBA8888)
|
||||
# img.fill(Qt.red)
|
||||
return img, img.size()
|
||||
|
||||
|
||||
def getUrl(base: str, path: str) -> dict:
|
||||
url: str = parse.urljoin(base, path)
|
||||
url: str = urllib.parse.urljoin(base, path)
|
||||
r: requests.Response = requests.get(url)
|
||||
return r.json()
|
||||
|
||||
|
||||
class Provider(QObject):
|
||||
def __init__(self, url: str, parent=None):
|
||||
def __init__(self, url: str, data_dir: str, parent=None):
|
||||
super().__init__(parent)
|
||||
self.url: str = url
|
||||
|
||||
@@ -89,8 +108,47 @@ class Provider(QObject):
|
||||
describe["icon"]
|
||||
)
|
||||
|
||||
# Create dictionary of show episodes
|
||||
episodes: dict = getUrl(self.url, "episodes")
|
||||
_episodes: defaultdict[str, list[Episode]] = defaultdict(lambda: [])
|
||||
|
||||
for e in episodes["episodes"]:
|
||||
_episodes[e["ShowTitle"]].append(Episode(e))
|
||||
|
||||
shows: dict = getUrl(self.url, "shows")
|
||||
self._shows: dict[int, Show] = {e["id"]: Show(e) for e in shows["data"]}
|
||||
|
||||
# Create image_cache directory
|
||||
provider_data_dir: str = os.path.join(
|
||||
data_dir, "image_cache", "providers", self._name
|
||||
)
|
||||
os.makedirs(provider_data_dir, exist_ok=True)
|
||||
|
||||
# Cache show posters
|
||||
for s in shows["data"]:
|
||||
# Get local cache path
|
||||
poster_url: str = s["poster"]
|
||||
poster_path: str = urllib.parse.urlparse(poster_url).path
|
||||
poster_ext: str = os.path.splitext(poster_path)[1]
|
||||
download_path: str = os.path.join(
|
||||
provider_data_dir, str(s["id"]) + poster_ext
|
||||
)
|
||||
|
||||
# Download poster
|
||||
try:
|
||||
with open(download_path, "xb") as f:
|
||||
r = requests.get(poster_url, stream=True)
|
||||
if r.status_code == 200:
|
||||
r.raw.decode_content = True
|
||||
shutil.copyfileobj(r.raw, f)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
# Overwrite poster URL with local path
|
||||
s["poster"] = download_path
|
||||
|
||||
self._shows: dict[int, Show] = {
|
||||
e["id"]: Show(e, _episodes[e["title"]]) for e in shows["data"]
|
||||
}
|
||||
|
||||
recently_added: dict = getUrl(self.url, "recently_added")
|
||||
self._recently_added: list[int] = recently_added["data"]
|
||||
@@ -106,7 +164,6 @@ class Provider(QObject):
|
||||
def logo(self) -> str:
|
||||
return f"image://{self._name}/logo"
|
||||
|
||||
# @pyqtProperty("QObject")
|
||||
@pyqtSlot(int, result=QObject)
|
||||
def getShow(self, id) -> Show:
|
||||
return self._shows[id]
|
||||
@@ -131,8 +188,8 @@ class Provider(QObject):
|
||||
|
||||
|
||||
class DataSource:
|
||||
def __init__(self, providers=[]):
|
||||
self.providers: list[Provider] = [Provider(url) for url in providers]
|
||||
def __init__(self, providers: list[str], data_dir: str):
|
||||
self.providers: list[Provider] = [Provider(url, data_dir) for url in providers]
|
||||
|
||||
|
||||
def DatabaseType(data_source) -> Type:
|
||||
@@ -149,6 +206,42 @@ def DatabaseType(data_source) -> Type:
|
||||
return Database
|
||||
|
||||
|
||||
def load_config() -> list[str]:
|
||||
try:
|
||||
config_dir: str = os.path.join(os.environ["XDG_CONFIG_HOME"], "ikinuki")
|
||||
except:
|
||||
config_dir: str = os.path.join(os.environ["HOME"], ".config", "ikinuki")
|
||||
|
||||
os.makedirs(config_dir, exist_ok=True)
|
||||
|
||||
config_file: str = os.path.join(config_dir, "client.toml")
|
||||
|
||||
try:
|
||||
config: dict = toml.load(config_file)
|
||||
except FileNotFoundError:
|
||||
print(f'Config file not found at "{config_file}"')
|
||||
print("Writing example config file. Please update and relaunch.")
|
||||
default_config = """# [[backends]]
|
||||
# address = "127.0.0.1"
|
||||
# port = 32520"""
|
||||
with open(config_file, "w") as f:
|
||||
f.write(default_config)
|
||||
sys.exit(-1)
|
||||
|
||||
return [f'http://{b["address"]}:{b["port"]}/' for b in config["backends"]]
|
||||
|
||||
|
||||
def get_data_dir() -> str:
|
||||
try:
|
||||
data_dir: str = os.path.join(os.environ["XDG_DATA_HOME"], "ikinuki")
|
||||
except:
|
||||
data_dir: str = os.path.join(os.environ["HOME"], ".local", "share", "ikinuki")
|
||||
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
|
||||
return data_dir
|
||||
|
||||
|
||||
def main():
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
@@ -157,16 +250,17 @@ def main():
|
||||
|
||||
locale.setlocale(locale.LC_NUMERIC, "C")
|
||||
|
||||
data_source = DataSource(
|
||||
[
|
||||
"http://127.0.0.1:8080/a/",
|
||||
"http://127.0.0.1:8080/b/",
|
||||
"http://127.0.0.1:8080/c/",
|
||||
]
|
||||
)
|
||||
try:
|
||||
backends: list[str] = load_config()
|
||||
except Exception as e:
|
||||
print(f"ERROR: Could not load config file: {repr(e)}")
|
||||
sys.exit(-1)
|
||||
|
||||
data_dir: str = get_data_dir()
|
||||
|
||||
data_source = DataSource(backends, data_dir)
|
||||
|
||||
qmlRegisterType(DatabaseType(data_source), "Ikinuki.Client", 1, 0, "Database")
|
||||
# qmlRegisterType(Provider, "Ikinuki.Client", 1, 0, "Provider")
|
||||
|
||||
engine = QQmlApplicationEngine()
|
||||
for provider in data_source.providers:
|
||||
@@ -182,24 +276,3 @@ def main():
|
||||
win.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
# app = QApplication([])
|
||||
#
|
||||
#
|
||||
# window = QQmlApplicationEngine("layouts/mpv.qml")
|
||||
# window.run
|
||||
#
|
||||
# view = QQuickView()
|
||||
# url = QUrl("layouts/mpv.qml")
|
||||
#
|
||||
# import locale
|
||||
#
|
||||
# locale.setlocale(locale.LC_NUMERIC, 'C')
|
||||
#
|
||||
# view.setSource(url)
|
||||
# view.show()
|
||||
# app.exec_()
|
||||
|
||||
@@ -18,7 +18,7 @@ Row {
|
||||
ContentView {
|
||||
id: view
|
||||
viewSelected: selectedView == 1
|
||||
currentIndex: selectedProvider + (browse ? db.Providers.length : 0)
|
||||
parentIndex: selectedProvider + (browse ? db.Providers.length : 0)
|
||||
providers: db.Providers
|
||||
}
|
||||
function mod(n, m) {
|
||||
|
||||
@@ -11,8 +11,14 @@ StackLayout {
|
||||
id: tabView
|
||||
property var providers: []
|
||||
property bool viewSelected
|
||||
state: viewSelected ? "selected" : "deselected"
|
||||
property int parentIndex
|
||||
|
||||
property int ySelect: 0
|
||||
property bool showViewActive: false
|
||||
|
||||
currentIndex: showViewActive ? tabView.children.length - 3 : parentIndex
|
||||
|
||||
state: viewSelected ? "selected" : "deselected"
|
||||
width: parent.width * viewSelected ? 0.95 : 0.8
|
||||
height: parent.height
|
||||
|
||||
@@ -28,6 +34,10 @@ StackLayout {
|
||||
provider: modelData
|
||||
}
|
||||
}
|
||||
ShowView {
|
||||
id: showView
|
||||
show: providers[0].getShow(providers[0].showsAlphabetic[0])
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: "deselected"
|
||||
@@ -54,15 +64,33 @@ StackLayout {
|
||||
}
|
||||
]
|
||||
|
||||
Keys.onPressed: (event)=> {
|
||||
function getCurrentIndex() {
|
||||
if (showViewActive) {
|
||||
var x = tabView.children.length - 1;
|
||||
} else {
|
||||
var x = currentIndex > (providers.length - 1) ? currentIndex + 1 : currentIndex;
|
||||
//tabView.children[currentIndex].Keys.pressed(event);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
Keys.onPressed: (event)=> {
|
||||
var x = getCurrentIndex()
|
||||
tabView.children[x].Keys.pressed(event);
|
||||
if (tabView.children[x].viewExit) {
|
||||
if (tabView.children[x].enterShow) {
|
||||
showViewActive = true;
|
||||
showView.show = tabView.children[x].enterShowShow;
|
||||
} else if (tabView.children[x].viewExit) {
|
||||
if (showViewActive) {
|
||||
showViewActive = false;
|
||||
tabView.children[x].xIndex = 0;
|
||||
tabView.children[x].viewExit = false;
|
||||
tabView.children[getCurrentIndex()].enterShow = false;
|
||||
} else {
|
||||
tabView.children[x].viewExit = false;
|
||||
parent.browse = false;
|
||||
parent.selectedView = 0;
|
||||
}
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import Ikinuki.Client 1.0
|
||||
|
||||
import "./ShowView"
|
||||
|
||||
ScrollView {
|
||||
id: root
|
||||
property var episodeModel
|
||||
property int scrollIndex: 0
|
||||
contentHeight: root.height * 0.1 * episodeModel.length
|
||||
ScrollBar.vertical.position: scrollIndex / (episodeModel.length)
|
||||
clip: true
|
||||
Behavior on ScrollBar.vertical.position {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.Linear
|
||||
}
|
||||
}
|
||||
Column {
|
||||
Repeater {
|
||||
model: episodeModel
|
||||
Episode {
|
||||
episode: modelData
|
||||
selected: index == xIndex
|
||||
height: root.height * 0.1
|
||||
width: root.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ Rectangle {
|
||||
id: root
|
||||
property var provider
|
||||
property bool viewExit: false
|
||||
property bool enterShow: false
|
||||
property var enterShowShow
|
||||
property int currentView: 0
|
||||
color: "#22282A"
|
||||
Row {
|
||||
@@ -76,6 +78,9 @@ Rectangle {
|
||||
coverGrid.scrollIndex--;
|
||||
}
|
||||
}
|
||||
} else if (event.key == Qt.Key_Return) {
|
||||
enterShowShow = root.provider.getShow(root.provider.showsAlphabetic[(coverGrid.xIndex + coverGrid.yIndex * coverGrid.numColumns)])
|
||||
enterShow = true;
|
||||
}
|
||||
} else if (currentView == 1) { // alphabet
|
||||
if (event.key == Qt.Key_Left) {
|
||||
|
||||
@@ -11,8 +11,9 @@ Rectangle {
|
||||
id: root
|
||||
property var provider
|
||||
property int ySelect: 0
|
||||
property int xSelect: 0
|
||||
property bool viewExit: false
|
||||
property bool enterShow: false
|
||||
property var enterShowShow
|
||||
color: "#22282A"
|
||||
Row {
|
||||
Item {
|
||||
@@ -82,6 +83,9 @@ Rectangle {
|
||||
ySelect++;
|
||||
} else if (event.key == Qt.Key_Up) {
|
||||
ySelect--;
|
||||
} else if (event.key == Qt.Key_Return) {
|
||||
enterShowShow = provider.getShow(elementColumn.children[ySelect].showId)
|
||||
enterShow = true;
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import Ikinuki.Client 1.0
|
||||
|
||||
import "./ShowView"
|
||||
|
||||
Rectangle {
|
||||
property var show
|
||||
property bool viewExit: false
|
||||
property bool enterShow: false
|
||||
property int xIndex: 0
|
||||
color: "#22282A"
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: parent.width * 0.01
|
||||
anchors.rightMargin: parent.width * 0.01
|
||||
anchors.topMargin: parent.height * 0.05
|
||||
anchors.bottomMargin: parent.height * 0.01
|
||||
Text { // header
|
||||
height: parent.height * 0.1
|
||||
width: parent.width
|
||||
text: show.title
|
||||
font.pointSize: 20
|
||||
color: "#cdd7d9"
|
||||
}
|
||||
Row { // main view
|
||||
height: parent.height * 0.8
|
||||
width: parent.width
|
||||
spacing: parent.width * 0.01
|
||||
Item {
|
||||
height: parent.height
|
||||
width: height * 0.68
|
||||
Image {
|
||||
source: show.poster
|
||||
anchors.fill: parent
|
||||
mipmap: true
|
||||
}
|
||||
}
|
||||
EpisodeView {
|
||||
id: episodeView
|
||||
height: parent.height
|
||||
width: parent.width * 0.65
|
||||
episodeModel: show.episodes
|
||||
}
|
||||
}
|
||||
Item {
|
||||
height: parent.height * 0.1
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: (event)=> {
|
||||
if (event.key == Qt.Key_Up) {
|
||||
if (xIndex > 0) {
|
||||
xIndex--;
|
||||
if (episodeView.scrollIndex > xIndex) {
|
||||
episodeView.scrollIndex--;
|
||||
}
|
||||
}
|
||||
} else if (event.key == Qt.Key_Down) {
|
||||
if (xIndex < show.episodes.length - 1) {
|
||||
if (episodeView.scrollIndex < (xIndex - 8)) {
|
||||
episodeView.scrollIndex++;
|
||||
}
|
||||
xIndex++;
|
||||
}
|
||||
} else if (event.key == Qt.Key_Escape) {
|
||||
viewExit = true;
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import Ikinuki.Client 1.0
|
||||
|
||||
Item {
|
||||
property var episode
|
||||
property bool selected
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "white"
|
||||
visible: selected
|
||||
radius: 10
|
||||
}
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
Item {
|
||||
height: parent.height
|
||||
width: parent.width * 0.04
|
||||
}
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: 4
|
||||
Text {
|
||||
text: {
|
||||
if (modelData.season == 0) {
|
||||
return "S" + modelData.episode + " " + modelData.title
|
||||
} else {
|
||||
return String(modelData.episode).padStart(2, "0") + ". " + modelData.title
|
||||
}
|
||||
}
|
||||
font.pointSize: 20
|
||||
color: selected ? "#3c3c3c" : "#99afb4"
|
||||
}
|
||||
Item {
|
||||
height: parent.height * 0.1
|
||||
width: parent.width
|
||||
}
|
||||
Text {
|
||||
text: " air date"
|
||||
font.pointSize: 12
|
||||
color: selected ? "#3c3c3c" : "#99afb4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ Item {
|
||||
DropShadow {
|
||||
anchors.fill: selector
|
||||
verticalOffset: 5
|
||||
//horizontalOffset: 5
|
||||
samples: 20
|
||||
color: "black"
|
||||
opacity: 0.5
|
||||
@@ -24,7 +23,6 @@ Item {
|
||||
color: "white"
|
||||
height: parent.height
|
||||
width: parent.width * 0.95
|
||||
//anchors.fill: parent
|
||||
visible: selected && maximized
|
||||
radius: 10
|
||||
}
|
||||
@@ -42,11 +40,6 @@ Item {
|
||||
anchors.fill: parent
|
||||
id: logo
|
||||
source: provider.logo
|
||||
//y: parent.y + (parent.height / 2) - height / 2
|
||||
//anchors.top: parent.verticalCenter
|
||||
// anchors.verticalCenter: text.verticalCenter
|
||||
//sourceSize.height: 50
|
||||
//sourceSize.width: 50
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: logo
|
||||
@@ -66,8 +59,6 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: 4
|
||||
visible: maximized
|
||||
//verticalAlignment: Text.AlignVCenter
|
||||
//height: parent.height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+14
-2
@@ -62,7 +62,7 @@ optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.extras]
|
||||
screenshot_raw = ["pillow"]
|
||||
screenshot_raw = ["Pillow"]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
@@ -82,6 +82,14 @@ urllib3 = ">=1.21.1,<1.27"
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.12"
|
||||
@@ -98,7 +106,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "7e77956c19bb0148f37be8283857fbd29805184149d6f2c1027daab624c66b80"
|
||||
content-hash = "ab4b6be20253adf487e007f81b07f9f585bd4a61d4e51889f9356dfc0e03e206"
|
||||
|
||||
[metadata.files]
|
||||
certifi = [
|
||||
@@ -152,6 +160,10 @@ requests = [
|
||||
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
||||
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
||||
]
|
||||
toml = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
|
||||
{file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
|
||||
|
||||
@@ -10,6 +10,7 @@ python = "^3.10"
|
||||
python-mpv = "^0.5.2"
|
||||
PyQt5 = "^5.15.1"
|
||||
requests = "^2.28.1"
|
||||
toml = "^0.10.2"
|
||||
|
||||
|
||||
[tool.poetry.scripts]
|
||||
|
||||
Reference in New Issue
Block a user