Preparation for 2.0 release
This commit is contained in:
parent
65eb9bd7bf
commit
14821ab759
10 changed files with 231 additions and 212 deletions
66
customtkinter.pyi
Normal file
66
customtkinter.pyi
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
from tkinter import Tk
|
||||||
|
from typing import Any, Callable, Optional
|
||||||
|
|
||||||
|
from PIL.Image import Image
|
||||||
|
|
||||||
|
class CTk(Tk):
|
||||||
|
def __init__(self, parent: Optional[Tk] = None, className: str = "Tk") -> None:
|
||||||
|
pass
|
||||||
|
def pack(
|
||||||
|
self,
|
||||||
|
expand: bool = False,
|
||||||
|
fill: str = "",
|
||||||
|
side: str = "",
|
||||||
|
padx: int = 0,
|
||||||
|
pady: int = 0,
|
||||||
|
ipadx: int = 0,
|
||||||
|
ipady: int = 0,
|
||||||
|
anchor: str = "",
|
||||||
|
) -> None: ...
|
||||||
|
def grid(
|
||||||
|
self, column: int, row: int, padx: int = 0, pady: int = 0, sticky: str = ""
|
||||||
|
) -> None: ...
|
||||||
|
def configure(self, **kwargs: Any) -> None: ...
|
||||||
|
|
||||||
|
class CTkToplevel(CTk): ...
|
||||||
|
class CTkFrame(CTk): ...
|
||||||
|
|
||||||
|
class CTkImage:
|
||||||
|
def __init__(self, light_image: Image, size: tuple[int, int]) -> None: ...
|
||||||
|
|
||||||
|
class CTkTabview(CTk):
|
||||||
|
def __init__(self, parent: Tk, width: int, height: int) -> None: ...
|
||||||
|
def add(self, name: str) -> None: ...
|
||||||
|
def set(self, name: str) -> None: ...
|
||||||
|
def tab(self, name: str) -> CTkFrame: ...
|
||||||
|
|
||||||
|
class CTkOptionMenu(CTk):
|
||||||
|
def __init__(self, parent: Tk, values: list[str]) -> None: ...
|
||||||
|
def set(self, value: str) -> None: ...
|
||||||
|
def get(self) -> str: ...
|
||||||
|
|
||||||
|
class CTkCheckBox(CTk):
|
||||||
|
def __init__(self, parent: Tk, text: str, onvalue: Any, offvalue: Any) -> None: ...
|
||||||
|
def select(self) -> None: ...
|
||||||
|
def deselect(self) -> None: ...
|
||||||
|
def get(self) -> Any: ...
|
||||||
|
|
||||||
|
class CTkLabel(CTk):
|
||||||
|
def __init__(self, parent: Tk, text: str, justify: str = "") -> None: ...
|
||||||
|
|
||||||
|
class CTkTextbox(CTk):
|
||||||
|
def __init__(self, parent: Tk, wrap: str = "none", height: int = 1) -> None: ...
|
||||||
|
def get(self, start: str, end: str) -> str: ...
|
||||||
|
def delete(self, start: str, end: str) -> None: ...
|
||||||
|
def insert(self, start: str, value: str) -> None: ...
|
||||||
|
|
||||||
|
class CTkScrollableFrame(CTk): ...
|
||||||
|
|
||||||
|
class CTkButton(CTk):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent: Tk,
|
||||||
|
text: str,
|
||||||
|
command: Callable[..., None],
|
||||||
|
width: Optional[int] = None,
|
||||||
|
) -> None: ...
|
|
@ -6,6 +6,7 @@
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import sphinx_rtd_theme
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath(".."))
|
sys.path.insert(0, os.path.abspath(".."))
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ release = "2.0.0"
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||||
|
|
||||||
extensions = ["sphinx.ext.autodoc"]
|
extensions = ["sphinx.ext.autodoc", "sphinx_rtd_theme"]
|
||||||
|
|
||||||
templates_path = ["_templates"]
|
templates_path = ["_templates"]
|
||||||
exclude_patterns = []
|
exclude_patterns = []
|
||||||
|
@ -26,5 +27,5 @@ exclude_patterns = []
|
||||||
# -- Options for HTML output -------------------------------------------------
|
# -- Options for HTML output -------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||||
|
|
||||||
html_theme = "alabaster"
|
html_theme = "sphinx_rtd_theme"
|
||||||
html_static_path = ["_static"]
|
html_static_path = ["_static"]
|
||||||
|
|
|
@ -30,6 +30,9 @@ pyyaml = "^6.0.1"
|
||||||
async-tkinter-loop = "^0.9.2"
|
async-tkinter-loop = "^0.9.2"
|
||||||
tkcalendar = "^1.6.1"
|
tkcalendar = "^1.6.1"
|
||||||
tktimepicker = "^2.0.2"
|
tktimepicker = "^2.0.2"
|
||||||
|
types-pyyaml = "^6.0.12.12"
|
||||||
|
types-pillow = "^10.1.0.2"
|
||||||
|
platformdirs = "^4.0.0"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
|
@ -40,17 +43,20 @@ exclude = [ ".venv" ]
|
||||||
venvPath = "."
|
venvPath = "."
|
||||||
venv = ".venv"
|
venv = ".venv"
|
||||||
|
|
||||||
|
[tool.pylint."MESSAGES CONTROL"]
|
||||||
|
disable = '''too-many-lines,
|
||||||
|
too-many-ancestors
|
||||||
|
'''
|
||||||
|
|
||||||
[[tool.mypy.overrides]]
|
[[tool.mypy.overrides]]
|
||||||
module = [
|
module = [
|
||||||
"aiohttp",
|
|
||||||
"pytube",
|
|
||||||
"minio",
|
|
||||||
"aiocmd",
|
|
||||||
"pyqrcodeng",
|
|
||||||
"socketio",
|
|
||||||
"pillow",
|
|
||||||
"PIL",
|
|
||||||
"yt_dlp",
|
"yt_dlp",
|
||||||
|
"pymediainfo",
|
||||||
|
"minio",
|
||||||
|
"qrcode",
|
||||||
|
"engineio",
|
||||||
|
"tkcalendar",
|
||||||
|
"tktimepicker"
|
||||||
]
|
]
|
||||||
ignore_missing_imports = true
|
ignore_missing_imports = true
|
||||||
|
|
||||||
|
|
|
@ -16,19 +16,16 @@ Excerp from the help::
|
||||||
--config-file CONFIG_FILE, -C CONFIG_FILE
|
--config-file CONFIG_FILE, -C CONFIG_FILE
|
||||||
--key KEY, -k KEY
|
--key KEY, -k KEY
|
||||||
|
|
||||||
The config file should be a json file in the following style::
|
The config file should be a yaml file in the following style::
|
||||||
|
|
||||||
{
|
sources:
|
||||||
"sources": {
|
SOURCE1:
|
||||||
"SOURCE1": { configuration for SOURCE },
|
configuration for SOURCE
|
||||||
"SOURCE2": { configuration for SOURCE },
|
SOURCE2:
|
||||||
|
configuration for SOURCE
|
||||||
...
|
...
|
||||||
},
|
config:
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
configuration for the client
|
configuration for the client
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -40,21 +37,19 @@ import signal
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from dataclasses import field
|
from dataclasses import field
|
||||||
from yaml import load, Loader
|
|
||||||
from traceback import print_exc
|
from traceback import print_exc
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import qrcode
|
import qrcode
|
||||||
|
|
||||||
import socketio
|
import socketio
|
||||||
import engineio
|
import engineio
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from yaml import load, Loader
|
||||||
|
|
||||||
from . import jsonencoder
|
from . import jsonencoder
|
||||||
from .entry import Entry
|
from .entry import Entry
|
||||||
from .sources import configure_sources
|
from .sources import configure_sources, Source
|
||||||
from .sources import Source
|
|
||||||
|
|
||||||
|
|
||||||
sio: socketio.AsyncClient = socketio.AsyncClient(json=jsonencoder)
|
sio: socketio.AsyncClient = socketio.AsyncClient(json=jsonencoder)
|
||||||
|
@ -106,7 +101,7 @@ class State:
|
||||||
* `last_song` (`Optional[datetime.datetime]`): A timestamp, defining the end of
|
* `last_song` (`Optional[datetime.datetime]`): A timestamp, defining the end of
|
||||||
the queue.
|
the queue.
|
||||||
* `waiting_room_policy` (Optional[str]): One of:
|
* `waiting_room_policy` (Optional[str]): One of:
|
||||||
- `force`, if a performer is already in the queue, they are put in the
|
- `forced`, if a performer is already in the queue, they are put in the
|
||||||
waiting room.
|
waiting room.
|
||||||
- `optional`, if a performer is already in the queue, they have the option
|
- `optional`, if a performer is already in the queue, they have the option
|
||||||
to be put in the waiting room.
|
to be put in the waiting room.
|
||||||
|
@ -200,12 +195,8 @@ async def handle_connect() -> None:
|
||||||
"queue": state.queue,
|
"queue": state.queue,
|
||||||
"waiting_room": state.waiting_room,
|
"waiting_room": state.waiting_room,
|
||||||
"recent": state.recent,
|
"recent": state.recent,
|
||||||
# "room": state.config["room"],
|
|
||||||
# "secret": state.config["secret"],
|
|
||||||
"config": state.config,
|
"config": state.config,
|
||||||
}
|
}
|
||||||
if state.config["key"]:
|
|
||||||
data["registration-key"] = state.config["key"] # TODO: unify
|
|
||||||
await sio.emit("register-client", data)
|
await sio.emit("register-client", data)
|
||||||
|
|
||||||
|
|
||||||
|
@ -357,9 +348,7 @@ async def handle_request_config(data: dict[str, Any]) -> None:
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
if data["source"] in sources:
|
if data["source"] in sources:
|
||||||
config: dict[str, Any] | list[dict[str, Any]] = await sources[
|
config: dict[str, Any] | list[dict[str, Any]] = await sources[data["source"]].get_config()
|
||||||
data["source"]
|
|
||||||
].get_config()
|
|
||||||
if isinstance(config, list):
|
if isinstance(config, list):
|
||||||
num_chunks: int = len(config)
|
num_chunks: int = len(config)
|
||||||
for current, chunk in enumerate(config):
|
for current, chunk in enumerate(config):
|
||||||
|
@ -376,7 +365,7 @@ async def handle_request_config(data: dict[str, Any]) -> None:
|
||||||
await sio.emit("config", {"source": data["source"], "config": config})
|
await sio.emit("config", {"source": data["source"], "config": config})
|
||||||
|
|
||||||
|
|
||||||
def signal_handler():
|
def signal_handler() -> None:
|
||||||
engineio.async_client.async_signal_handler()
|
engineio.async_client.async_signal_handler()
|
||||||
if state.current_source is not None:
|
if state.current_source is not None:
|
||||||
if state.current_source.player is not None:
|
if state.current_source.player is not None:
|
||||||
|
@ -426,7 +415,7 @@ async def start_client(config: dict[str, Any]) -> None:
|
||||||
state.current_source.player.kill()
|
state.current_source.player.kill()
|
||||||
|
|
||||||
|
|
||||||
def create_async_and_start_client(config):
|
def create_async_and_start_client(config: dict[str, Any]) -> None:
|
||||||
asyncio.run(start_client(config))
|
asyncio.run(start_client(config))
|
||||||
|
|
||||||
|
|
||||||
|
|
208
syng/gui.py
208
syng/gui.py
|
@ -1,4 +1,4 @@
|
||||||
import asyncio
|
from multiprocessing import Process
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from datetime import datetime, date, time
|
from datetime import datetime, date, time
|
||||||
import os
|
import os
|
||||||
|
@ -6,19 +6,19 @@ import builtins
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import PIL
|
|
||||||
from yaml import load, Loader
|
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import customtkinter
|
|
||||||
import qrcode
|
|
||||||
import secrets
|
import secrets
|
||||||
import string
|
import string
|
||||||
from tkinter import PhotoImage, Tk, filedialog
|
|
||||||
from tkcalendar import Calendar
|
|
||||||
from tktimepicker import SpinTimePickerOld, AnalogPicker, AnalogThemes
|
|
||||||
from tktimepicker import constants
|
|
||||||
|
|
||||||
from .client import create_async_and_start_client, default_config, start_client
|
from PIL import ImageTk
|
||||||
|
from yaml import dump, load, Loader, Dumper
|
||||||
|
import customtkinter
|
||||||
|
from qrcode import QRCode
|
||||||
|
from tkcalendar import Calendar
|
||||||
|
from tktimepicker import AnalogPicker, AnalogThemes, constants
|
||||||
|
import platformdirs
|
||||||
|
|
||||||
|
from .client import create_async_and_start_client, default_config
|
||||||
|
|
||||||
from .sources import available_sources
|
from .sources import available_sources
|
||||||
from .server import main as server_main
|
from .server import main as server_main
|
||||||
|
@ -57,9 +57,7 @@ class DateAndTimePickerWindow(customtkinter.CTkToplevel):
|
||||||
|
|
||||||
self.timepicker.pack(expand=True, fill="both")
|
self.timepicker.pack(expand=True, fill="both")
|
||||||
|
|
||||||
button = customtkinter.CTkButton(
|
button = customtkinter.CTkButton(self, text="Ok", command=partial(self.insert, input_field))
|
||||||
self, text="Ok", command=partial(self.insert, input_field)
|
|
||||||
)
|
|
||||||
button.pack(expand=True, fill="x")
|
button.pack(expand=True, fill="x")
|
||||||
|
|
||||||
def insert(self, input_field: customtkinter.CTkTextbox) -> None:
|
def insert(self, input_field: customtkinter.CTkTextbox) -> None:
|
||||||
|
@ -105,17 +103,13 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
description: str,
|
description: str,
|
||||||
value: str = "",
|
value: str = "",
|
||||||
callback: Optional[Callable[..., None]] = None,
|
callback: Optional[Callable[..., None]] = None,
|
||||||
):
|
) -> None:
|
||||||
self.add_option_label(description)
|
self.add_option_label(description)
|
||||||
if value is None:
|
if value is None:
|
||||||
value = ""
|
value = ""
|
||||||
|
|
||||||
self.string_options[name] = customtkinter.CTkTextbox(
|
self.string_options[name] = customtkinter.CTkTextbox(self, wrap="none", height=1)
|
||||||
self, wrap="none", height=1
|
self.string_options[name].grid(column=1, row=self.number_of_options, sticky="EW")
|
||||||
)
|
|
||||||
self.string_options[name].grid(
|
|
||||||
column=1, row=self.number_of_options, sticky="EW"
|
|
||||||
)
|
|
||||||
self.string_options[name].insert("0.0", value)
|
self.string_options[name].insert("0.0", value)
|
||||||
if callback is not None:
|
if callback is not None:
|
||||||
self.string_options[name].bind("<KeyRelease>", callback)
|
self.string_options[name].bind("<KeyRelease>", callback)
|
||||||
|
@ -160,7 +154,7 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
description: str,
|
description: str,
|
||||||
value: list[str] = [],
|
value: list[str],
|
||||||
callback: Optional[Callable[..., None]] = None,
|
callback: Optional[Callable[..., None]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.add_option_label(description)
|
self.add_option_label(description)
|
||||||
|
@ -185,32 +179,26 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
) -> None:
|
) -> None:
|
||||||
self.add_option_label(description)
|
self.add_option_label(description)
|
||||||
self.choose_options[name] = customtkinter.CTkOptionMenu(self, values=values)
|
self.choose_options[name] = customtkinter.CTkOptionMenu(self, values=values)
|
||||||
self.choose_options[name].grid(
|
self.choose_options[name].grid(column=1, row=self.number_of_options, sticky="EW")
|
||||||
column=1, row=self.number_of_options, sticky="EW"
|
|
||||||
)
|
|
||||||
self.choose_options[name].set(value)
|
self.choose_options[name].set(value)
|
||||||
self.number_of_options += 1
|
self.number_of_options += 1
|
||||||
|
|
||||||
def open_date_and_time_picker(
|
def open_date_and_time_picker(self, name: str, input_field: customtkinter.CTkTextbox) -> None:
|
||||||
self, name: str, input_field: customtkinter.CTkTextbox
|
|
||||||
) -> None:
|
|
||||||
if (
|
if (
|
||||||
name not in self.date_and_time_pickers
|
name not in self.date_and_time_pickers
|
||||||
or not self.date_and_time_pickers[name].winfo_exists()
|
or not self.date_and_time_pickers[name].winfo_exists()
|
||||||
):
|
):
|
||||||
self.date_and_time_pickers[name] = DateAndTimePickerWindow(
|
self.date_and_time_pickers[name] = DateAndTimePickerWindow(self, input_field)
|
||||||
self, input_field
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.date_and_time_pickers[name].focus()
|
self.date_and_time_pickers[name].focus()
|
||||||
|
|
||||||
def add_date_time_option(self, name: str, description: str, value: str) -> None:
|
def add_date_time_option(self, name: str, description: str, value: str) -> None:
|
||||||
self.add_option_label(description)
|
self.add_option_label(description)
|
||||||
self.date_time_options[name] = None
|
|
||||||
input_and_button = customtkinter.CTkFrame(self)
|
input_and_button = customtkinter.CTkFrame(self)
|
||||||
input_and_button.grid(column=1, row=self.number_of_options, sticky="EW")
|
input_and_button.grid(column=1, row=self.number_of_options, sticky="EW")
|
||||||
input_field = customtkinter.CTkTextbox(input_and_button, wrap="none", height=1)
|
input_field = customtkinter.CTkTextbox(input_and_button, wrap="none", height=1)
|
||||||
input_field.pack(side="left", fill="x", expand=True)
|
input_field.pack(side="left", fill="x", expand=True)
|
||||||
|
self.date_time_options[name] = input_field
|
||||||
try:
|
try:
|
||||||
datetime.fromisoformat(value)
|
datetime.fromisoformat(value)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -229,16 +217,16 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
def __init__(self, parent: customtkinter.CTkFrame) -> None:
|
def __init__(self, parent: customtkinter.CTkFrame) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.columnconfigure((1,), weight=1)
|
self.columnconfigure((1,), weight=1)
|
||||||
self.number_of_options = 0
|
self.number_of_options: int = 0
|
||||||
self.string_options = {}
|
self.string_options: dict[str, customtkinter.CTkTextbox] = {}
|
||||||
self.choose_options = {}
|
self.choose_options: dict[str, customtkinter.CTkOptionMenu] = {}
|
||||||
self.bool_options = {}
|
self.bool_options: dict[str, customtkinter.CTkCheckBox] = {}
|
||||||
self.list_options = {}
|
self.list_options: dict[str, list[customtkinter.CTkTextbox]] = {}
|
||||||
self.date_time_options = {}
|
self.date_time_options: dict[str, customtkinter.CTkTextbox] = {}
|
||||||
self.date_and_time_pickers = {}
|
self.date_and_time_pickers: dict[str, DateAndTimePickerWindow] = {}
|
||||||
|
|
||||||
def get_config(self) -> dict[str, Any]:
|
def get_config(self) -> dict[str, Any]:
|
||||||
config = {}
|
config: dict[str, Any] = {}
|
||||||
for name, textbox in self.string_options.items():
|
for name, textbox in self.string_options.items():
|
||||||
config[name] = textbox.get("0.0", "end").strip()
|
config[name] = textbox.get("0.0", "end").strip()
|
||||||
|
|
||||||
|
@ -253,6 +241,9 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
for textbox in textboxes:
|
for textbox in textboxes:
|
||||||
config[name].append(textbox.get("0.0", "end").strip())
|
config[name].append(textbox.get("0.0", "end").strip())
|
||||||
|
|
||||||
|
for name, picker in self.date_time_options.items():
|
||||||
|
config[name] = picker.get("0.0", "end").strip()
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@ -293,9 +284,7 @@ class GeneralConfig(OptionFrame):
|
||||||
str(config["waiting_room_policy"]).lower(),
|
str(config["waiting_room_policy"]).lower(),
|
||||||
)
|
)
|
||||||
self.add_date_time_option("last_song", "Time of last song", config["last_song"])
|
self.add_date_time_option("last_song", "Time of last song", config["last_song"])
|
||||||
self.add_string_option(
|
self.add_string_option("preview_duration", "Preview Duration", config["preview_duration"])
|
||||||
"preview_duration", "Preview Duration", config["preview_duration"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_config(self) -> dict[str, Any]:
|
def get_config(self) -> dict[str, Any]:
|
||||||
config = super().get_config()
|
config = super().get_config()
|
||||||
|
@ -308,15 +297,12 @@ class GeneralConfig(OptionFrame):
|
||||||
|
|
||||||
|
|
||||||
class SyngGui(customtkinter.CTk):
|
class SyngGui(customtkinter.CTk):
|
||||||
def loadConfig(self) -> None:
|
|
||||||
filedialog.askopenfilename()
|
|
||||||
|
|
||||||
def on_close(self) -> None:
|
def on_close(self) -> None:
|
||||||
if self.server is not None:
|
if self.syng_server is not None:
|
||||||
self.server.kill()
|
self.syng_server.kill()
|
||||||
|
|
||||||
if self.client is not None:
|
if self.syng_client is not None:
|
||||||
self.client.kill()
|
self.syng_client.kill()
|
||||||
|
|
||||||
self.withdraw()
|
self.withdraw()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
@ -326,21 +312,30 @@ class SyngGui(customtkinter.CTk):
|
||||||
self.protocol("WM_DELETE_WINDOW", self.on_close)
|
self.protocol("WM_DELETE_WINDOW", self.on_close)
|
||||||
|
|
||||||
rel_path = os.path.dirname(__file__)
|
rel_path = os.path.dirname(__file__)
|
||||||
img = PIL.ImageTk.PhotoImage(file=os.path.join(rel_path, "static/syng.png"))
|
img = ImageTk.PhotoImage(file=os.path.join(rel_path, "static/syng.png"))
|
||||||
self.wm_iconbitmap()
|
self.wm_iconbitmap()
|
||||||
self.iconphoto(False, img)
|
self.iconphoto(False, img)
|
||||||
|
|
||||||
self.server = None
|
self.syng_server: Optional[Process] = None
|
||||||
self.client = None
|
self.syng_client: Optional[Process] = None
|
||||||
|
|
||||||
|
self.configfile = os.path.join(platformdirs.user_config_dir("syng"), "config.yaml")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open("syng-client.yaml") as cfile:
|
with open(self.configfile, encoding="utf8") as cfile:
|
||||||
loaded_config = load(cfile, Loader=Loader)
|
loaded_config = load(cfile, Loader=Loader)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
print("No config found, using default values")
|
||||||
loaded_config = {}
|
loaded_config = {}
|
||||||
config = {"sources": {}, "config": default_config()}
|
config: dict[str, dict[str, Any]] = {"sources": {}, "config": default_config()}
|
||||||
if "config" in loaded_config:
|
|
||||||
|
try:
|
||||||
config["config"] |= loaded_config["config"]
|
config["config"] |= loaded_config["config"]
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
print("Could not load config")
|
||||||
|
|
||||||
|
# if "config" in loaded_config:
|
||||||
|
# config["config"] |= loaded_config["config"]
|
||||||
|
|
||||||
if not config["config"]["secret"]:
|
if not config["config"]["secret"]:
|
||||||
config["config"]["secret"] = "".join(
|
config["config"]["secret"] = "".join(
|
||||||
|
@ -350,30 +345,26 @@ class SyngGui(customtkinter.CTk):
|
||||||
self.wm_title("Syng")
|
self.wm_title("Syng")
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
fileframe = customtkinter.CTkFrame(self)
|
button_line = customtkinter.CTkFrame(self)
|
||||||
fileframe.pack(side="bottom")
|
button_line.pack(side="bottom", fill="x")
|
||||||
|
|
||||||
loadbutton = customtkinter.CTkButton(
|
startsyng_serverbutton = customtkinter.CTkButton(
|
||||||
fileframe,
|
button_line, text="Start Local Server", command=self.start_syng_server
|
||||||
text="load",
|
|
||||||
command=self.loadConfig,
|
|
||||||
)
|
)
|
||||||
loadbutton.pack(side="left")
|
startsyng_serverbutton.pack(side="left", expand=True, anchor="w", padx=10, pady=5)
|
||||||
|
|
||||||
self.startbutton = customtkinter.CTkButton(
|
savebutton = customtkinter.CTkButton(button_line, text="Save", command=self.save_config)
|
||||||
fileframe, text="Start", command=self.start_client
|
savebutton.pack(side="left", padx=10, pady=5)
|
||||||
)
|
|
||||||
self.startbutton.pack(side="right")
|
|
||||||
|
|
||||||
# startserverbutton = customtkinter.CTkButton(
|
|
||||||
# fileframe, text="Start Server", command=self.start_server
|
|
||||||
# )
|
|
||||||
# startserverbutton.pack(side="right")
|
|
||||||
|
|
||||||
open_web_button = customtkinter.CTkButton(
|
open_web_button = customtkinter.CTkButton(
|
||||||
fileframe, text="Open Web", command=self.open_web
|
button_line, text="Open Web", command=self.open_web
|
||||||
)
|
)
|
||||||
open_web_button.pack(side="left")
|
open_web_button.pack(side="left", pady=5)
|
||||||
|
|
||||||
|
self.startbutton = customtkinter.CTkButton(
|
||||||
|
button_line, text="Save and Start", command=self.start_syng_client
|
||||||
|
)
|
||||||
|
self.startbutton.pack(side="left", padx=10, pady=10)
|
||||||
|
|
||||||
# Tabs and QR Code
|
# Tabs and QR Code
|
||||||
frm = customtkinter.CTkFrame(self)
|
frm = customtkinter.CTkFrame(self)
|
||||||
|
@ -391,7 +382,7 @@ class SyngGui(customtkinter.CTk):
|
||||||
self.qrlabel.pack(side="left", anchor="n", padx=10, pady=10)
|
self.qrlabel.pack(side="left", anchor="n", padx=10, pady=10)
|
||||||
|
|
||||||
self.general_config = GeneralConfig(
|
self.general_config = GeneralConfig(
|
||||||
tabview.tab("General"), config["config"], self.updateQr
|
tabview.tab("General"), config["config"], self.update_qr
|
||||||
)
|
)
|
||||||
self.general_config.pack(ipadx=10, fill="both", expand=True)
|
self.general_config.pack(ipadx=10, fill="both", expand=True)
|
||||||
|
|
||||||
|
@ -400,62 +391,67 @@ class SyngGui(customtkinter.CTk):
|
||||||
for source_name in available_sources:
|
for source_name in available_sources:
|
||||||
try:
|
try:
|
||||||
source_config = loaded_config["sources"][source_name]
|
source_config = loaded_config["sources"][source_name]
|
||||||
except KeyError:
|
except (KeyError, TypeError):
|
||||||
source_config = {}
|
source_config = {}
|
||||||
|
|
||||||
self.tabs[source_name] = SourceTab(
|
self.tabs[source_name] = SourceTab(tabview.tab(source_name), source_name, source_config)
|
||||||
tabview.tab(source_name), source_name, source_config
|
|
||||||
)
|
|
||||||
self.tabs[source_name].pack(ipadx=10, expand=True, fill="both")
|
self.tabs[source_name].pack(ipadx=10, expand=True, fill="both")
|
||||||
|
|
||||||
self.updateQr()
|
self.update_qr()
|
||||||
|
|
||||||
def start_client(self) -> None:
|
def save_config(self) -> None:
|
||||||
if self.client is None:
|
with open(self.configfile, "w", encoding="utf-8") as f:
|
||||||
sources = {}
|
dump(self.gather_config(), f, Dumper=Dumper)
|
||||||
for source, tab in self.tabs.items():
|
|
||||||
sources[source] = tab.get_config()
|
|
||||||
|
|
||||||
general_config = self.general_config.get_config()
|
def gather_config(self) -> dict[str, Any]:
|
||||||
|
sources = {}
|
||||||
|
for source, tab in self.tabs.items():
|
||||||
|
sources[source] = tab.get_config()
|
||||||
|
|
||||||
config = {"sources": sources, "config": general_config}
|
general_config = self.general_config.get_config()
|
||||||
self.client = multiprocessing.Process(
|
|
||||||
|
return {"sources": sources, "config": general_config}
|
||||||
|
|
||||||
|
def start_syng_client(self) -> None:
|
||||||
|
if self.syng_client is None:
|
||||||
|
config = self.gather_config()
|
||||||
|
self.syng_client = multiprocessing.Process(
|
||||||
target=create_async_and_start_client, args=(config,)
|
target=create_async_and_start_client, args=(config,)
|
||||||
)
|
)
|
||||||
self.client.start()
|
self.syng_client.start()
|
||||||
self.startbutton.configure(text="Stop")
|
self.startbutton.configure(text="Stop")
|
||||||
else:
|
else:
|
||||||
self.client.terminate()
|
self.syng_client.terminate()
|
||||||
self.client = None
|
self.syng_client = None
|
||||||
self.startbutton.configure(text="Start")
|
self.startbutton.configure(text="Save and Start")
|
||||||
|
|
||||||
def start_server(self) -> None:
|
def start_syng_server(self) -> None:
|
||||||
self.server = multiprocessing.Process(target=server_main)
|
self.syng_server = multiprocessing.Process(target=server_main)
|
||||||
self.server.start()
|
self.syng_server.start()
|
||||||
|
|
||||||
def open_web(self) -> None:
|
def open_web(self) -> None:
|
||||||
config = self.general_config.get_config()
|
config = self.general_config.get_config()
|
||||||
server = config["server"]
|
syng_server = config["server"]
|
||||||
server += "" if server.endswith("/") else "/"
|
syng_server += "" if syng_server.endswith("/") else "/"
|
||||||
room = config["room"]
|
room = config["room"]
|
||||||
webbrowser.open(server + room)
|
webbrowser.open(syng_server + room)
|
||||||
|
|
||||||
def changeQr(self, data: str) -> None:
|
def change_qr(self, data: str) -> None:
|
||||||
qr = qrcode.QRCode(box_size=20, border=2)
|
qr = QRCode(box_size=20, border=2)
|
||||||
qr.add_data(data)
|
qr.add_data(data)
|
||||||
qr.make()
|
qr.make()
|
||||||
qr.print_ascii()
|
qr.print_ascii()
|
||||||
image = qr.make_image().convert("RGB")
|
image = qr.make_image().convert("RGB")
|
||||||
tkQrcode = customtkinter.CTkImage(light_image=image, size=(280, 280))
|
tk_qrcode = customtkinter.CTkImage(light_image=image, size=(280, 280))
|
||||||
self.qrlabel.configure(image=tkQrcode)
|
self.qrlabel.configure(image=tk_qrcode)
|
||||||
|
|
||||||
def updateQr(self, _evt: None = None) -> None:
|
def update_qr(self, _evt: None = None) -> None:
|
||||||
config = self.general_config.get_config()
|
config = self.general_config.get_config()
|
||||||
server = config["server"]
|
syng_server = config["server"]
|
||||||
server += "" if server.endswith("/") else "/"
|
syng_server += "" if syng_server.endswith("/") else "/"
|
||||||
room = config["room"]
|
room = config["room"]
|
||||||
print(server + room)
|
print(syng_server + room)
|
||||||
self.changeQr(server + room)
|
self.change_qr(syng_server + room)
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
|
0
syng/py.typed
Normal file
0
syng/py.typed
Normal file
|
@ -90,7 +90,7 @@ class Client:
|
||||||
in the calculation of the ETA for songs later in the queue.
|
in the calculation of the ETA for songs later in the queue.
|
||||||
* `last_song` (`Optional[float]`): A timestamp, defining the end of the queue.
|
* `last_song` (`Optional[float]`): A timestamp, defining the end of the queue.
|
||||||
* `waiting_room_policy` (Optional[str]): One of:
|
* `waiting_room_policy` (Optional[str]): One of:
|
||||||
- `force`, if a performer is already in the queue, they are put in the
|
- `forced`, if a performer is already in the queue, they are put in the
|
||||||
waiting room.
|
waiting room.
|
||||||
- `optional`, if a performer is already in the queue, they have the option
|
- `optional`, if a performer is already in the queue, they have the option
|
||||||
to be put in the waiting room.
|
to be put in the waiting room.
|
||||||
|
@ -132,9 +132,7 @@ class State:
|
||||||
recent: list[Entry]
|
recent: list[Entry]
|
||||||
sid: str
|
sid: str
|
||||||
client: Client
|
client: Client
|
||||||
last_seen: datetime.datetime = field(
|
last_seen: datetime.datetime = field(init=False, default_factory=datetime.datetime.now)
|
||||||
init=False, default_factory=datetime.datetime.now
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
clients: dict[str, State] = {}
|
clients: dict[str, State] = {}
|
||||||
|
@ -156,13 +154,17 @@ async def send_state(state: State, sid: str) -> None:
|
||||||
:type sid: str:
|
:type sid: str:
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
safe_config = {k: v for k, v in state.client.config.items() if k not in ["secret", "key"]}
|
||||||
|
print(safe_config)
|
||||||
|
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"state",
|
"state",
|
||||||
{
|
{
|
||||||
"queue": state.queue,
|
"queue": state.queue,
|
||||||
"recent": state.recent,
|
"recent": state.recent,
|
||||||
"waiting_room": state.waiting_room,
|
"waiting_room": state.waiting_room,
|
||||||
"config": state.client.config,
|
"config": safe_config,
|
||||||
},
|
},
|
||||||
room=sid,
|
room=sid,
|
||||||
)
|
)
|
||||||
|
@ -205,18 +207,13 @@ async def handle_waiting_room_append(sid: str, data: dict[str, Any]) -> None:
|
||||||
if entry is None:
|
if entry is None:
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"msg",
|
"msg",
|
||||||
{
|
{"msg": f"Unable to add to the waiting room: {data['ident']}. Maybe try again?"},
|
||||||
"msg": f"Unable to add to the waiting room: {data['ident']}. Maybe try again?"
|
|
||||||
},
|
|
||||||
room=sid,
|
room=sid,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if "uid" not in data or (
|
if "uid" not in data or (
|
||||||
(
|
(data["uid"] is not None and len(list(state.queue.find_by_uid(data["uid"]))) == 0)
|
||||||
data["uid"] is not None
|
|
||||||
and len(list(state.queue.find_by_uid(data["uid"]))) == 0
|
|
||||||
)
|
|
||||||
or (data["uid"] is None and state.queue.find_by_name(data["performer"]) is None)
|
or (data["uid"] is None and state.queue.find_by_name(data["performer"]) is None)
|
||||||
):
|
):
|
||||||
await append_to_queue(room, entry, sid)
|
await append_to_queue(room, entry, sid)
|
||||||
|
@ -233,9 +230,7 @@ async def handle_waiting_room_append(sid: str, data: dict[str, Any]) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def append_to_queue(
|
async def append_to_queue(room: str, entry: Entry, report_to: Optional[str] = None) -> None:
|
||||||
room: str, entry: Entry, report_to: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Append a song to the queue for a given session.
|
Append a song to the queue for a given session.
|
||||||
|
|
||||||
|
@ -259,10 +254,7 @@ async def append_to_queue(
|
||||||
start_time = first_song.started_at
|
start_time = first_song.started_at
|
||||||
|
|
||||||
start_time = state.queue.fold(
|
start_time = state.queue.fold(
|
||||||
lambda item, time: time
|
lambda item, time: time + item.duration + state.client.config["preview_duration"] + 1,
|
||||||
+ item.duration
|
|
||||||
+ state.client.config["preview_duration"]
|
|
||||||
+ 1,
|
|
||||||
start_time,
|
start_time,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -384,7 +376,7 @@ async def handle_append(sid: str, data: dict[str, Any]) -> None:
|
||||||
state = clients[room]
|
state = clients[room]
|
||||||
|
|
||||||
if state.client.config["waiting_room_policy"] and (
|
if state.client.config["waiting_room_policy"] and (
|
||||||
state.client.config["waiting_room_policy"].lower() == "force"
|
state.client.config["waiting_room_policy"].lower() == "forced"
|
||||||
or state.client.config["waiting_room_policy"].lower() == "optional"
|
or state.client.config["waiting_room_policy"].lower() == "optional"
|
||||||
):
|
):
|
||||||
old_entry = state.queue.find_by_name(data["performer"])
|
old_entry = state.queue.find_by_name(data["performer"])
|
||||||
|
@ -437,7 +429,7 @@ async def handle_append_anyway(sid: str, data: dict[str, Any]) -> None:
|
||||||
room = session["room"]
|
room = session["room"]
|
||||||
state = clients[room]
|
state = clients[room]
|
||||||
|
|
||||||
if state.client.config["waiting_room_policy"].lower() == "force":
|
if state.client.config["waiting_room_policy"].lower() == "forced":
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"err",
|
"err",
|
||||||
{"type": "WAITING_ROOM_FORCED"},
|
{"type": "WAITING_ROOM_FORCED"},
|
||||||
|
@ -546,11 +538,7 @@ async def handle_waiting_room_to_queue(sid: str, data: dict[str, Any]) -> None:
|
||||||
|
|
||||||
if is_admin:
|
if is_admin:
|
||||||
entry = next(
|
entry = next(
|
||||||
(
|
(wr_entry for wr_entry in state.waiting_room if str(wr_entry.uuid) == data["uuid"]),
|
||||||
wr_entry
|
|
||||||
for wr_entry in state.waiting_room
|
|
||||||
if str(wr_entry.uuid) == data["uuid"]
|
|
||||||
),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
if entry is not None:
|
if entry is not None:
|
||||||
|
@ -682,22 +670,19 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def gen_id(length: int = 4) -> str:
|
def gen_id(length: int = 4) -> str:
|
||||||
client_id = "".join(
|
client_id = "".join([random.choice(string.ascii_letters) for _ in range(length)])
|
||||||
[random.choice(string.ascii_letters) for _ in range(length)]
|
|
||||||
)
|
|
||||||
if client_id in clients:
|
if client_id in clients:
|
||||||
client_id = gen_id(length + 1)
|
client_id = gen_id(length + 1)
|
||||||
return client_id
|
return client_id
|
||||||
|
|
||||||
if not app["public"]:
|
if not app["public"]:
|
||||||
with open(app["registration-keyfile"]) as f:
|
with open(app["registration-keyfile"], encoding="utf8") as f:
|
||||||
raw_keys = f.readlines()
|
raw_keys = f.readlines()
|
||||||
keys = [key[:64] for key in raw_keys]
|
keys = [key[:64] for key in raw_keys]
|
||||||
|
|
||||||
if (
|
if (
|
||||||
"registration-key" not in data
|
"key" not in data["config"]
|
||||||
or hashlib.sha256(data["registration-key"].encode()).hexdigest()
|
or hashlib.sha256(data["config"]["key"].encode()).hexdigest() not in keys
|
||||||
not in keys
|
|
||||||
):
|
):
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"client-registered",
|
"client-registered",
|
||||||
|
@ -707,9 +692,7 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
room: str = (
|
room: str = (
|
||||||
data["config"]["room"]
|
data["config"]["room"] if "room" in data["config"] and data["config"]["room"] else gen_id()
|
||||||
if "room" in data["config"] and data["config"]["room"]
|
|
||||||
else gen_id()
|
|
||||||
)
|
)
|
||||||
async with sio.session(sid) as session:
|
async with sio.session(sid) as session:
|
||||||
session["room"] = room
|
session["room"] = room
|
||||||
|
@ -725,15 +708,11 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None:
|
||||||
config=DEFAULT_CONFIG | data["config"],
|
config=DEFAULT_CONFIG | data["config"],
|
||||||
)
|
)
|
||||||
await sio.enter_room(sid, room)
|
await sio.enter_room(sid, room)
|
||||||
await sio.emit(
|
await sio.emit("client-registered", {"success": True, "room": room}, room=sid)
|
||||||
"client-registered", {"success": True, "room": room}, room=sid
|
|
||||||
)
|
|
||||||
await send_state(clients[room], sid)
|
await send_state(clients[room], sid)
|
||||||
else:
|
else:
|
||||||
logger.warning("Got wrong secret for %s", room)
|
logger.warning("Got wrong secret for %s", room)
|
||||||
await sio.emit(
|
await sio.emit("client-registered", {"success": False, "room": room}, room=sid)
|
||||||
"client-registered", {"success": False, "room": room}, room=sid
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
logger.info("Registerd new client %s", room)
|
logger.info("Registerd new client %s", room)
|
||||||
initial_entries = [Entry(**entry) for entry in data["queue"]]
|
initial_entries = [Entry(**entry) for entry in data["queue"]]
|
||||||
|
@ -824,9 +803,7 @@ async def handle_config_chunk(sid: str, data: dict[str, Any]) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if data["source"] not in state.client.sources:
|
if data["source"] not in state.client.sources:
|
||||||
state.client.sources[data["source"]] = available_sources[data["source"]](
|
state.client.sources[data["source"]] = available_sources[data["source"]](data["config"])
|
||||||
data["config"]
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
state.client.sources[data["source"]].add_to_config(data["config"])
|
state.client.sources[data["source"]].add_to_config(data["config"])
|
||||||
|
|
||||||
|
@ -855,9 +832,7 @@ async def handle_config(sid: str, data: dict[str, Any]) -> None:
|
||||||
if sid != state.sid:
|
if sid != state.sid:
|
||||||
return
|
return
|
||||||
|
|
||||||
state.client.sources[data["source"]] = available_sources[data["source"]](
|
state.client.sources[data["source"]] = available_sources[data["source"]](data["config"])
|
||||||
data["config"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@sio.on("register-web")
|
@sio.on("register-web")
|
||||||
|
@ -1042,17 +1017,10 @@ async def handle_search(sid: str, data: dict[str, Any]) -> None:
|
||||||
|
|
||||||
query = data["query"]
|
query = data["query"]
|
||||||
results_list = await asyncio.gather(
|
results_list = await asyncio.gather(
|
||||||
*[
|
*[state.client.sources[source].search(query) for source in state.client.sources_prio]
|
||||||
state.client.sources[source].search(query)
|
|
||||||
for source in state.client.sources_prio
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
results = [
|
results = [search_result for source_result in results_list for search_result in source_result]
|
||||||
search_result
|
|
||||||
for source_result in results_list
|
|
||||||
for search_result in source_result
|
|
||||||
]
|
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"search-results",
|
"search-results",
|
||||||
{"results": results},
|
{"results": results},
|
||||||
|
@ -1119,7 +1087,7 @@ def main() -> None:
|
||||||
"""
|
"""
|
||||||
parser = ArgumentParser()
|
parser = ArgumentParser()
|
||||||
parser.add_argument("--host", "-H", default="localhost")
|
parser.add_argument("--host", "-H", default="localhost")
|
||||||
parser.add_argument("--port", "-p", default="8080")
|
parser.add_argument("--port", "-p", type=int, default=8080)
|
||||||
parser.add_argument("--root-folder", "-r", default="syng/static/")
|
parser.add_argument("--root-folder", "-r", default="syng/static/")
|
||||||
parser.add_argument("--registration-keyfile", "-k", default=None)
|
parser.add_argument("--registration-keyfile", "-k", default=None)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
@ -1131,9 +1099,7 @@ def main() -> None:
|
||||||
|
|
||||||
app["root_folder"] = args.root_folder
|
app["root_folder"] = args.root_folder
|
||||||
|
|
||||||
app.add_routes(
|
app.add_routes([web.static("/assets/", os.path.join(app["root_folder"], "assets/"))])
|
||||||
[web.static("/assets/", os.path.join(app["root_folder"], "assets/"))]
|
|
||||||
)
|
|
||||||
|
|
||||||
app.router.add_route("*", "/", root_handler)
|
app.router.add_route("*", "/", root_handler)
|
||||||
app.router.add_route("*", "/{room}", root_handler)
|
app.router.add_route("*", "/{room}", root_handler)
|
||||||
|
|
|
@ -66,7 +66,8 @@ class FileBasedSource(Source):
|
||||||
info: str | MediaInfo = MediaInfo.parse(file)
|
info: str | MediaInfo = MediaInfo.parse(file)
|
||||||
if isinstance(info, str):
|
if isinstance(info, str):
|
||||||
return 180
|
return 180
|
||||||
return info.audio_tracks[0].to_data()["duration"] // 1000
|
duration: int = info.audio_tracks[0].to_data()["duration"]
|
||||||
|
return duration // 1000
|
||||||
|
|
||||||
video_path, audio_path = self.get_video_audio_split(path)
|
video_path, audio_path = self.get_video_audio_split(path)
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ class S3Source(FileBasedSource):
|
||||||
|
|
||||||
await self.ensure_playable(entry)
|
await self.ensure_playable(entry)
|
||||||
|
|
||||||
file_name: Optional[str] = self.downloaded_files[entry.ident].video
|
file_name: str = self.downloaded_files[entry.ident].video
|
||||||
|
|
||||||
duration = await self.get_duration(file_name)
|
duration = await self.get_duration(file_name)
|
||||||
|
|
||||||
|
|
|
@ -120,9 +120,7 @@ class Source(ABC):
|
||||||
source for documentation.
|
source for documentation.
|
||||||
:type config: dict[str, Any]
|
:type config: dict[str, Any]
|
||||||
"""
|
"""
|
||||||
self.downloaded_files: defaultdict[str, DLFilesEntry] = defaultdict(
|
self.downloaded_files: defaultdict[str, DLFilesEntry] = defaultdict(DLFilesEntry)
|
||||||
DLFilesEntry
|
|
||||||
)
|
|
||||||
self._masterlock: asyncio.Lock = asyncio.Lock()
|
self._masterlock: asyncio.Lock = asyncio.Lock()
|
||||||
self.player: Optional[asyncio.subprocess.Process] = None
|
self.player: Optional[asyncio.subprocess.Process] = None
|
||||||
self._index: list[str] = config["index"] if "index" in config else []
|
self._index: list[str] = config["index"] if "index" in config else []
|
||||||
|
@ -145,9 +143,7 @@ class Source(ABC):
|
||||||
:returns: An async reference to the process
|
:returns: An async reference to the process
|
||||||
:rtype: asyncio.subprocess.Process
|
:rtype: asyncio.subprocess.Process
|
||||||
"""
|
"""
|
||||||
args = ["--fullscreen", *options, video] + (
|
args = ["--fullscreen", *options, video] + ([f"--audio-file={audio}"] if audio else [])
|
||||||
[f"--audio-file={audio}"] if audio else []
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"File is {video=} and {audio=}")
|
print(f"File is {video=} and {audio=}")
|
||||||
|
|
||||||
|
@ -231,7 +227,6 @@ class Source(ABC):
|
||||||
:returns: A Tuple of the locations for the video and the audio file.
|
:returns: A Tuple of the locations for the video and the audio file.
|
||||||
:rtype: Tuple[str, Optional[str]]
|
:rtype: Tuple[str, Optional[str]]
|
||||||
"""
|
"""
|
||||||
...
|
|
||||||
|
|
||||||
async def buffer(self, entry: Entry) -> None:
|
async def buffer(self, entry: Entry) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -407,7 +402,6 @@ class Source(ABC):
|
||||||
:return: The part of the config, that should be sended to the server.
|
:return: The part of the config, that should be sended to the server.
|
||||||
:rtype: dict[str, Any] | list[dict[str, Any]]
|
:rtype: dict[str, Any] | list[dict[str, Any]]
|
||||||
"""
|
"""
|
||||||
print("xzy")
|
|
||||||
if not self._index:
|
if not self._index:
|
||||||
self._index = []
|
self._index = []
|
||||||
print(f"{self.source_name}: generating index")
|
print(f"{self.source_name}: generating index")
|
||||||
|
|
Loading…
Add table
Reference in a new issue