Reworked Config Schemas, added File/Folder-Picker and dedicated Int-Spinners
This commit is contained in:
parent
9279a6a5a2
commit
eb725c7c33
7 changed files with 229 additions and 50 deletions
48
syng/config.py
Normal file
48
syng/config.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Generic, TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
class Option(Generic[T]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ConfigOption(Generic[T]):
|
||||||
|
type: Option[T]
|
||||||
|
description: str
|
||||||
|
default: T
|
||||||
|
|
||||||
|
|
||||||
|
class BoolOption(Option[bool]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class IntOption(Option[int]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class StrOption(Option[str]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordOption(Option[str]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FolderOption(Option[str]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FileOption(Option[str]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ListStrOption(Option[list[str]]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ChoiceOption(Option[str]):
|
||||||
|
choices: list[str]
|
160
syng/gui.py
160
syng/gui.py
|
@ -32,6 +32,7 @@ from PyQt6.QtWidgets import (
|
||||||
QCheckBox,
|
QCheckBox,
|
||||||
QComboBox,
|
QComboBox,
|
||||||
QDateTimeEdit,
|
QDateTimeEdit,
|
||||||
|
QFileDialog,
|
||||||
QFormLayout,
|
QFormLayout,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QLabel,
|
QLabel,
|
||||||
|
@ -40,6 +41,7 @@ from PyQt6.QtWidgets import (
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QSizePolicy,
|
QSizePolicy,
|
||||||
QSpacerItem,
|
QSpacerItem,
|
||||||
|
QSpinBox,
|
||||||
QTabWidget,
|
QTabWidget,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QWidget,
|
QWidget,
|
||||||
|
@ -52,6 +54,16 @@ from . import resources # noqa
|
||||||
from .client import create_async_and_start_client, default_config
|
from .client import create_async_and_start_client, default_config
|
||||||
|
|
||||||
from .sources import available_sources
|
from .sources import available_sources
|
||||||
|
from .config import (
|
||||||
|
BoolOption,
|
||||||
|
ChoiceOption,
|
||||||
|
FileOption,
|
||||||
|
FolderOption,
|
||||||
|
IntOption,
|
||||||
|
ListStrOption,
|
||||||
|
PasswordOption,
|
||||||
|
StrOption,
|
||||||
|
)
|
||||||
|
|
||||||
# try:
|
# try:
|
||||||
# from .server import run_server
|
# from .server import run_server
|
||||||
|
@ -72,7 +84,6 @@ class OptionFrame(QWidget):
|
||||||
self.bool_options[name] = QCheckBox(self)
|
self.bool_options[name] = QCheckBox(self)
|
||||||
self.bool_options[name].setChecked(value)
|
self.bool_options[name].setChecked(value)
|
||||||
self.form_layout.addRow(label, self.bool_options[name])
|
self.form_layout.addRow(label, self.bool_options[name])
|
||||||
self.number_of_options += 1
|
|
||||||
|
|
||||||
def add_string_option(
|
def add_string_option(
|
||||||
self,
|
self,
|
||||||
|
@ -105,9 +116,100 @@ class OptionFrame(QWidget):
|
||||||
|
|
||||||
self.string_options[name].insert(value)
|
self.string_options[name].insert(value)
|
||||||
self.form_layout.addRow(label, self.string_options[name])
|
self.form_layout.addRow(label, self.string_options[name])
|
||||||
|
self.rows[name] = (label, self.string_options[name])
|
||||||
if callback is not None:
|
if callback is not None:
|
||||||
self.string_options[name].textChanged.connect(callback)
|
self.string_options[name].textChanged.connect(callback)
|
||||||
self.number_of_options += 1
|
|
||||||
|
def path_setter(self, line: QLineEdit, name: Optional[str]) -> None:
|
||||||
|
if name:
|
||||||
|
line.setText(name)
|
||||||
|
|
||||||
|
def add_file_option(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
description: str,
|
||||||
|
value: Optional[str] = "",
|
||||||
|
callback: Optional[Callable[..., None]] = None,
|
||||||
|
) -> None:
|
||||||
|
if value is None:
|
||||||
|
value = ""
|
||||||
|
|
||||||
|
label = QLabel(description, self)
|
||||||
|
file_layout = QHBoxLayout()
|
||||||
|
file_name_widget = QLineEdit(value, self)
|
||||||
|
file_button = QPushButton(QIcon.fromTheme("document-open"), "", self)
|
||||||
|
|
||||||
|
file_button.clicked.connect(
|
||||||
|
lambda: self.path_setter(
|
||||||
|
file_name_widget,
|
||||||
|
QFileDialog.getOpenFileName(
|
||||||
|
self, "Select File", directory=os.path.dirname(file_name_widget.text())
|
||||||
|
)[0],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if callback is not None:
|
||||||
|
file_name_widget.textChanged.connect(callback)
|
||||||
|
|
||||||
|
file_layout.addWidget(file_name_widget)
|
||||||
|
file_layout.addWidget(file_button)
|
||||||
|
|
||||||
|
self.string_options[name] = file_name_widget
|
||||||
|
self.rows[name] = (label, file_name_widget)
|
||||||
|
self.form_layout.addRow(label, file_layout)
|
||||||
|
|
||||||
|
def add_folder_option(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
description: str,
|
||||||
|
value: Optional[str] = "",
|
||||||
|
callback: Optional[Callable[..., None]] = None,
|
||||||
|
) -> None:
|
||||||
|
if value is None:
|
||||||
|
value = ""
|
||||||
|
|
||||||
|
label = QLabel(description, self)
|
||||||
|
folder_layout = QHBoxLayout()
|
||||||
|
folder_name_widget = QLineEdit(value, self)
|
||||||
|
folder_button = QPushButton(QIcon.fromTheme("folder-open"), "", self)
|
||||||
|
|
||||||
|
folder_button.clicked.connect(
|
||||||
|
lambda: self.path_setter(
|
||||||
|
folder_name_widget,
|
||||||
|
QFileDialog.getExistingDirectory(
|
||||||
|
self, "Select Folder", directory=folder_name_widget.text()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if callback is not None:
|
||||||
|
folder_name_widget.textChanged.connect(callback)
|
||||||
|
|
||||||
|
folder_layout.addWidget(folder_name_widget)
|
||||||
|
folder_layout.addWidget(folder_button)
|
||||||
|
|
||||||
|
self.string_options[name] = folder_name_widget
|
||||||
|
self.rows[name] = (label, folder_name_widget)
|
||||||
|
self.form_layout.addRow(label, folder_layout)
|
||||||
|
|
||||||
|
def add_int_option(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
description: str,
|
||||||
|
value: Optional[int] = 0,
|
||||||
|
callback: Optional[Callable[..., None]] = None,
|
||||||
|
) -> None:
|
||||||
|
if value is None:
|
||||||
|
value = 0
|
||||||
|
|
||||||
|
label = QLabel(description, self)
|
||||||
|
|
||||||
|
self.int_options[name] = QSpinBox(self)
|
||||||
|
self.int_options[name].setValue(value)
|
||||||
|
self.form_layout.addRow(label, self.int_options[name])
|
||||||
|
self.rows[name] = (label, self.int_options[name])
|
||||||
|
if callback is not None:
|
||||||
|
self.int_options[name].textChanged.connect(callback)
|
||||||
|
|
||||||
def del_list_element(
|
def del_list_element(
|
||||||
self,
|
self,
|
||||||
|
@ -165,6 +267,7 @@ class OptionFrame(QWidget):
|
||||||
container_layout = QVBoxLayout()
|
container_layout = QVBoxLayout()
|
||||||
|
|
||||||
self.form_layout.addRow(label, container_layout)
|
self.form_layout.addRow(label, container_layout)
|
||||||
|
self.rows[name] = (label, container_layout)
|
||||||
|
|
||||||
self.list_options[name] = []
|
self.list_options[name] = []
|
||||||
for v in value:
|
for v in value:
|
||||||
|
@ -177,8 +280,6 @@ class OptionFrame(QWidget):
|
||||||
|
|
||||||
container_layout.addWidget(plus_button)
|
container_layout.addWidget(plus_button)
|
||||||
|
|
||||||
self.number_of_options += 1
|
|
||||||
|
|
||||||
def add_choose_option(
|
def add_choose_option(
|
||||||
self, name: str, description: str, values: list[str], value: str = ""
|
self, name: str, description: str, values: list[str], value: str = ""
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -186,9 +287,9 @@ class OptionFrame(QWidget):
|
||||||
|
|
||||||
self.choose_options[name] = QComboBox(self)
|
self.choose_options[name] = QComboBox(self)
|
||||||
self.choose_options[name].addItems(values)
|
self.choose_options[name].addItems(values)
|
||||||
self.choose_options[name].setCurrentText(value)
|
self.choose_options[name].setCurrentText(str(value))
|
||||||
self.form_layout.addRow(label, self.choose_options[name])
|
self.form_layout.addRow(label, self.choose_options[name])
|
||||||
self.number_of_options += 1
|
self.rows[name] = (label, self.choose_options[name])
|
||||||
|
|
||||||
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:
|
||||||
label = QLabel(description, self)
|
label = QLabel(description, self)
|
||||||
|
@ -213,25 +314,27 @@ class OptionFrame(QWidget):
|
||||||
date_time_layout.addWidget(date_time_enabled)
|
date_time_layout.addWidget(date_time_enabled)
|
||||||
|
|
||||||
self.form_layout.addRow(label, date_time_layout)
|
self.form_layout.addRow(label, date_time_layout)
|
||||||
|
self.rows[name] = (label, date_time_layout)
|
||||||
self.number_of_options += 1
|
|
||||||
|
|
||||||
def __init__(self, parent: Optional[QWidget] = None) -> None:
|
def __init__(self, parent: Optional[QWidget] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.form_layout = QFormLayout(self)
|
self.form_layout = QFormLayout(self)
|
||||||
self.setLayout(self.form_layout)
|
self.setLayout(self.form_layout)
|
||||||
self.number_of_options: int = 0
|
|
||||||
self.string_options: dict[str, QLineEdit] = {}
|
self.string_options: dict[str, QLineEdit] = {}
|
||||||
|
self.int_options: dict[str, QSpinBox] = {}
|
||||||
self.choose_options: dict[str, QComboBox] = {}
|
self.choose_options: dict[str, QComboBox] = {}
|
||||||
self.bool_options: dict[str, QCheckBox] = {}
|
self.bool_options: dict[str, QCheckBox] = {}
|
||||||
self.list_options: dict[str, list[QLineEdit]] = {}
|
self.list_options: dict[str, list[QLineEdit]] = {}
|
||||||
self.date_time_options: dict[str, tuple[QDateTimeEdit, QCheckBox]] = {}
|
self.date_time_options: dict[str, tuple[QDateTimeEdit, QCheckBox]] = {}
|
||||||
|
self.rows: dict[str, tuple[QLabel, QWidget | QLayout]] = {}
|
||||||
|
|
||||||
def get_config(self) -> dict[str, Any]:
|
def get_config(self) -> dict[str, Any]:
|
||||||
config: dict[str, Any] = {}
|
config: dict[str, Any] = {}
|
||||||
for name, textbox in self.string_options.items():
|
for name, textbox in self.string_options.items():
|
||||||
config[name] = textbox.text().strip()
|
config[name] = textbox.text().strip()
|
||||||
|
|
||||||
|
for name, textbox in self.int_options.items():
|
||||||
|
config[name] = textbox.value()
|
||||||
for name, optionmenu in self.choose_options.items():
|
for name, optionmenu in self.choose_options.items():
|
||||||
config[name] = optionmenu.currentText().strip()
|
config[name] = optionmenu.currentText().strip()
|
||||||
|
|
||||||
|
@ -260,17 +363,25 @@ class SourceTab(OptionFrame):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
source = available_sources[source_name]
|
source = available_sources[source_name]
|
||||||
self.vars: dict[str, str | bool | list[str]] = {}
|
self.vars: dict[str, str | bool | list[str]] = {}
|
||||||
for name, (typ, desc, default) in source.config_schema.items():
|
for name, option in source.config_schema.items():
|
||||||
value = config[name] if name in config else default
|
value = config[name] if name in config else option.default
|
||||||
match typ:
|
match option.type:
|
||||||
case builtins.bool:
|
case BoolOption():
|
||||||
self.add_bool_option(name, desc, value=value)
|
self.add_bool_option(name, option.description, value=value)
|
||||||
case builtins.list:
|
case ListStrOption():
|
||||||
self.add_list_option(name, desc, value=value)
|
self.add_list_option(name, option.description, value=value)
|
||||||
case builtins.str:
|
case StrOption():
|
||||||
self.add_string_option(name, desc, value=value)
|
self.add_string_option(name, option.description, value=value)
|
||||||
case "password":
|
case IntOption():
|
||||||
self.add_string_option(name, desc, value=value, is_password=True)
|
self.add_int_option(name, option.description, value=value)
|
||||||
|
case PasswordOption():
|
||||||
|
self.add_string_option(name, option.description, value=value, is_password=True)
|
||||||
|
case FolderOption():
|
||||||
|
self.add_folder_option(name, option.description, value=value)
|
||||||
|
case FileOption():
|
||||||
|
self.add_file_option(name, option.description, value=value)
|
||||||
|
case ChoiceOption(choices):
|
||||||
|
self.add_choose_option(name, option.description, choices, value)
|
||||||
|
|
||||||
|
|
||||||
class GeneralConfig(OptionFrame):
|
class GeneralConfig(OptionFrame):
|
||||||
|
@ -292,8 +403,8 @@ class GeneralConfig(OptionFrame):
|
||||||
str(config["waiting_room_policy"]).lower(),
|
str(config["waiting_room_policy"]).lower(),
|
||||||
)
|
)
|
||||||
self.add_date_time_option("last_song", "Last song ends at", config["last_song"])
|
self.add_date_time_option("last_song", "Last song ends at", config["last_song"])
|
||||||
self.add_string_option(
|
self.add_int_option(
|
||||||
"preview_duration", "Preview duration in seconds", str(config["preview_duration"])
|
"preview_duration", "Preview duration in seconds", int(config["preview_duration"])
|
||||||
)
|
)
|
||||||
self.add_string_option(
|
self.add_string_option(
|
||||||
"key", "Key for server (if necessary)", config["key"], is_password=True
|
"key", "Key for server (if necessary)", config["key"], is_password=True
|
||||||
|
@ -302,11 +413,6 @@ class GeneralConfig(OptionFrame):
|
||||||
|
|
||||||
def get_config(self) -> dict[str, Any]:
|
def get_config(self) -> dict[str, Any]:
|
||||||
config = super().get_config()
|
config = super().get_config()
|
||||||
try:
|
|
||||||
config["preview_duration"] = int(config["preview_duration"])
|
|
||||||
except ValueError:
|
|
||||||
config["preview_duration"] = 0
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import asyncio
|
||||||
import os
|
import os
|
||||||
from typing import TYPE_CHECKING, Any, Optional
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pymediainfo import MediaInfo
|
from pymediainfo import MediaInfo
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ except ImportError:
|
||||||
PYMEDIAINFO_AVAILABLE = False
|
PYMEDIAINFO_AVAILABLE = False
|
||||||
|
|
||||||
from .source import Source
|
from .source import Source
|
||||||
|
from ..config import ListStrOption, ConfigOption
|
||||||
|
|
||||||
|
|
||||||
class FileBasedSource(Source):
|
class FileBasedSource(Source):
|
||||||
|
@ -25,8 +27,8 @@ class FileBasedSource(Source):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config_schema = Source.config_schema | {
|
config_schema = Source.config_schema | {
|
||||||
"extensions": (
|
"extensions": ConfigOption(
|
||||||
list,
|
ListStrOption(),
|
||||||
"List of filename extensions\n(mp3+cdg, mp4, ...)",
|
"List of filename extensions\n(mp3+cdg, mp4, ...)",
|
||||||
["mp3+cdg"],
|
["mp3+cdg"],
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,9 +6,11 @@ from typing import Any, Optional
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from platformdirs import user_cache_dir
|
from platformdirs import user_cache_dir
|
||||||
|
|
||||||
|
|
||||||
from ..entry import Entry
|
from ..entry import Entry
|
||||||
from .source import available_sources
|
from .source import available_sources
|
||||||
from .filebased import FileBasedSource
|
from .filebased import FileBasedSource
|
||||||
|
from ..config import FolderOption, ConfigOption
|
||||||
|
|
||||||
|
|
||||||
class FilesSource(FileBasedSource):
|
class FilesSource(FileBasedSource):
|
||||||
|
@ -20,8 +22,8 @@ class FilesSource(FileBasedSource):
|
||||||
|
|
||||||
source_name = "files"
|
source_name = "files"
|
||||||
config_schema = FileBasedSource.config_schema | {
|
config_schema = FileBasedSource.config_schema | {
|
||||||
"dir": (str, "Directory to index", "."),
|
"dir": ConfigOption(FolderOption(), "Directory to index", "."),
|
||||||
"index_file": (str, "Index file", os.path.join(user_cache_dir("syng"), "files-index")),
|
# "index_file": ("file", "Index file", os.path.join(user_cache_dir("syng"), "files-index")),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, config: dict[str, Any]):
|
def __init__(self, config: dict[str, Any]):
|
||||||
|
|
|
@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any, Optional, Tuple, cast
|
||||||
|
|
||||||
from platformdirs import user_cache_dir
|
from platformdirs import user_cache_dir
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from minio import Minio
|
from minio import Minio
|
||||||
|
|
||||||
|
@ -23,6 +24,7 @@ except ImportError:
|
||||||
from ..entry import Entry
|
from ..entry import Entry
|
||||||
from .filebased import FileBasedSource
|
from .filebased import FileBasedSource
|
||||||
from .source import available_sources
|
from .source import available_sources
|
||||||
|
from ..config import BoolOption, ConfigOption, FileOption, FolderOption, PasswordOption, StrOption
|
||||||
|
|
||||||
|
|
||||||
class S3Source(FileBasedSource):
|
class S3Source(FileBasedSource):
|
||||||
|
@ -40,13 +42,19 @@ class S3Source(FileBasedSource):
|
||||||
|
|
||||||
source_name = "s3"
|
source_name = "s3"
|
||||||
config_schema = FileBasedSource.config_schema | {
|
config_schema = FileBasedSource.config_schema | {
|
||||||
"endpoint": (str, "Endpoint of the s3", ""),
|
"endpoint": ConfigOption(StrOption(), "Endpoint of the s3", ""),
|
||||||
"access_key": ("password", "Access Key of the s3", ""),
|
"access_key": ConfigOption(StrOption(), "Access Key of the s3 (username)", ""),
|
||||||
"secret_key": ("password", "Secret Key of the s3", ""),
|
"secret_key": ConfigOption(PasswordOption(), "Secret Key of the s3 (password)", ""),
|
||||||
"secure": (bool, "Use SSL", True),
|
"secure": ConfigOption(BoolOption(), "Use SSL", True),
|
||||||
"bucket": (str, "Bucket of the s3", ""),
|
"bucket": ConfigOption(StrOption(), "Bucket of the s3", ""),
|
||||||
"tmp_dir": (str, "Folder for\ntemporary download", user_cache_dir("syng")),
|
"tmp_dir": ConfigOption(
|
||||||
"index_file": (str, "Index file", os.path.join(user_cache_dir("syng"), "s3-index")),
|
FolderOption(), "Folder for\ntemporary download", user_cache_dir("syng")
|
||||||
|
),
|
||||||
|
"index_file": ConfigOption(
|
||||||
|
FileOption(),
|
||||||
|
"Index file",
|
||||||
|
os.path.join(user_cache_dir("syng"), "s3-index"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, config: dict[str, Any]):
|
def __init__(self, config: dict[str, Any]):
|
||||||
|
|
|
@ -21,9 +21,11 @@ from typing import Tuple
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
from ..log import logger
|
from ..log import logger
|
||||||
from ..entry import Entry
|
from ..entry import Entry
|
||||||
from ..result import Result
|
from ..result import Result
|
||||||
|
from ..config import BoolOption, ConfigOption
|
||||||
|
|
||||||
# logger: logging.Logger = logging.getLogger(__name__)
|
# logger: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -106,8 +108,8 @@ class Source(ABC):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
source_name: str = ""
|
source_name: str = ""
|
||||||
config_schema: dict[str, tuple[type | list[type] | str, str, Any]] = {
|
config_schema: dict[str, ConfigOption[Any]] = {
|
||||||
"enabled": (bool, "Enable this source", False)
|
"enabled": ConfigOption(BoolOption(), "Enable this source", False)
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, config: dict[str, Any]):
|
def __init__(self, config: dict[str, Any]):
|
||||||
|
|
|
@ -18,9 +18,11 @@ from yt_dlp import YoutubeDL
|
||||||
from yt_dlp.utils import DownloadError
|
from yt_dlp.utils import DownloadError
|
||||||
from platformdirs import user_cache_dir
|
from platformdirs import user_cache_dir
|
||||||
|
|
||||||
|
|
||||||
from ..entry import Entry
|
from ..entry import Entry
|
||||||
from ..result import Result
|
from ..result import Result
|
||||||
from .source import Source, available_sources
|
from .source import Source, available_sources
|
||||||
|
from ..config import BoolOption, ChoiceOption, FolderOption, ListStrOption, Option, ConfigOption
|
||||||
|
|
||||||
|
|
||||||
class YouTube:
|
class YouTube:
|
||||||
|
@ -28,9 +30,9 @@ class YouTube:
|
||||||
A minimal compatibility layer for the YouTube object of pytube, implemented via yt-dlp
|
A minimal compatibility layer for the YouTube object of pytube, implemented via yt-dlp
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__cache__: dict[
|
__cache__: dict[str, Any] = (
|
||||||
str, Any
|
{}
|
||||||
] = {} # TODO: this may grow fast... but atm it fixed youtubes anti bot measures
|
) # TODO: this may grow fast... but atm it fixed youtubes anti bot measures
|
||||||
|
|
||||||
def __init__(self, url: Optional[str] = None):
|
def __init__(self, url: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
|
@ -182,12 +184,18 @@ class YoutubeSource(Source):
|
||||||
|
|
||||||
source_name = "youtube"
|
source_name = "youtube"
|
||||||
config_schema = Source.config_schema | {
|
config_schema = Source.config_schema | {
|
||||||
"enabled": (bool, "Enable this source", True),
|
"enabled": ConfigOption(BoolOption(), "Enable this source", True),
|
||||||
"channels": (list, "A list channels\nto search in", []),
|
"channels": ConfigOption(ListStrOption(), "A list channels\nto search in", []),
|
||||||
"tmp_dir": (str, "Folder for\ntemporary download", user_cache_dir("syng")),
|
"tmp_dir": ConfigOption(
|
||||||
"max_res": (int, "Maximum resolution\nto download", 720),
|
FolderOption(), "Folder for\ntemporary download", user_cache_dir("syng")
|
||||||
"start_streaming": (
|
),
|
||||||
bool,
|
"max_res": ConfigOption(
|
||||||
|
ChoiceOption(["144", "240", "360", "480", "720", "1080", "2160"]),
|
||||||
|
"Maximum resolution\nto download",
|
||||||
|
"720",
|
||||||
|
),
|
||||||
|
"start_streaming": ConfigOption(
|
||||||
|
BoolOption(),
|
||||||
"Start streaming if\ndownload is not complete",
|
"Start streaming if\ndownload is not complete",
|
||||||
False,
|
False,
|
||||||
),
|
),
|
||||||
|
@ -205,7 +213,10 @@ class YoutubeSource(Source):
|
||||||
|
|
||||||
self.channels: list[str] = config["channels"] if "channels" in config else []
|
self.channels: list[str] = config["channels"] if "channels" in config else []
|
||||||
self.tmp_dir: str = config["tmp_dir"] if "tmp_dir" in config else "/tmp/syng"
|
self.tmp_dir: str = config["tmp_dir"] if "tmp_dir" in config else "/tmp/syng"
|
||||||
self.max_res: int = config["max_res"] if "max_res" in config else 720
|
try:
|
||||||
|
self.max_res: int = int(config["max_res"])
|
||||||
|
except (ValueError, KeyError):
|
||||||
|
self.max_res = 720
|
||||||
self.start_streaming: bool = (
|
self.start_streaming: bool = (
|
||||||
config["start_streaming"] if "start_streaming" in config else False
|
config["start_streaming"] if "start_streaming" in config else False
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue