Skip to content

Commit

Permalink
Merge pull request #41 from romanin-rf/dev
Browse files Browse the repository at this point in the history
Update 0.8.3
romanin-rf authored Dec 10, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents 9534024 + e8aadd3 commit 53bcf23
Showing 23 changed files with 200 additions and 28 deletions.
9 changes: 5 additions & 4 deletions build.py
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@
"seaplayer/objects/Buttons.py": "seaplayer/objects/",
"seaplayer/objects/Configurate.py": "seaplayer/objects/",
"seaplayer/objects/DataOptions.py": "seaplayer/objects/",
"seaplayer/objects/FullLabel.py": "seaplayer/objects/",
"seaplayer/objects/Labels.py": "seaplayer/objects/",
"seaplayer/objects/Image.py": "seaplayer/objects/",
"seaplayer/objects/Input.py": "seaplayer/objects/",
"seaplayer/objects/Log.py": "seaplayer/objects/",
@@ -67,9 +67,10 @@
"seaplayer/modules/colorizer.py": "seaplayer/modules/",
# * 3) CSS Files
"seaplayer/css": "seaplayer/css/",
"seaplayer/css/seaplayer.css": "seaplayer/css/",
"seaplayer/css/configurate.css": "seaplayer/css/",
"seaplayer/css/unknown.css": "seaplayer/css/",
"seaplayer/css/seaplayer.tcss": "seaplayer/css/",
"seaplayer/css/configurate.tcss": "seaplayer/css/",
"seaplayer/css/unknown.tcss": "seaplayer/css/",
"seaplayer/css/objects.tcss": "seaplayer/css/",
# * 4) Language Files
"seaplayer/langs": "seaplayer/langs/",
"seaplayer/langs/en-eng.properties": "seaplayer/langs/",
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@

| Version | Date | Tag | Changelog |
| ------- | ---- | --- | --------- |
| [v0.8.3](https://github.com/romanin-rf/SeaPlayer/releases/tag/v0.8.3) | 10.12.2023 | **STABLE** | - Added plugin `VKMusic`<br>- Added priority system for codecs<br>- Added system handhers of value<br>- Fixed `build.py`<br>- Moved method `load_plugin_info` in `seaplayer.plug.pluginloader` |
| [v0.8.2](https://github.com/romanin-rf/SeaPlayer/releases/tag/v0.8.2) | 08.12.2023 | **STABLE** | - Added language merge (for translating plugins)<br>- Fixed in translation files (`Log Menu Enable` -> `Logging' )<br>- Changed `object.css` (classes will no longer be used to specify standard properties)<br>- Improved widget `FillLabel` |
| [v0.8.1](https://github.com/romanin-rf/SeaPlayer/releases/tag/v0.8.1) | 07.12.2023 | **STABLE** | - Revisioned of the LanguageLoader<br>- Added new language: `Українська` |
| [v0.8.0](https://github.com/romanin-rf/SeaPlayer/releases/tag/v0.8.0) | 07.12.2023 | **STABLE** | - Added a new experimental `PopUp` widget (pop-up window)<br>- Added new exception type `Error`<br>- Added language loader system<br>- Added full translate: English, Русский<br>- Added `changelog.md`<br>- Added `secrets`<br>- Added widgets: `DataRadioButton`, `ClikableButton` and `Rheostat (experemental)`<br>- Fixed: `self.last_playback_status` not is int<br>- Changed CSS for `LogMenu`<br>- Updated `build.py`<br>- Renamed `*.css` files to `.tcss`<br>- Removing excess in `DataOptions` (from past updates)<br>- Deprecated method `seaplayer.functions.check_status`<br>- The first part of the attempts to make the `Configurate Screen` understandable to the average user |
2 changes: 1 addition & 1 deletion plugins/RichDiscordStatus/__init__.py
Original file line number Diff line number Diff line change
@@ -67,4 +67,4 @@ async def on_quit(self) -> None:
await self.thread.wait()

# ! Registration Plugin Class
plugin_main = RichDiscordStatus
__plugin__ = RichDiscordStatus
60 changes: 60 additions & 0 deletions plugins/VKMusic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import logging
from rich.console import Console
from rich.prompt import Prompt
from seaplayer.plug import PluginBase
from vkpymusic import Service, TokenReceiver
# > Local Imports
from .vkmcodec import VKMCodec
from .units import (
pcheck,
VKM_MAIN_PATTERN,
VKM_RANGE_PATTERN
)

# ! Logging Disable
logging.disable()

# ! Vars
console = Console()

# ! Plugin Value Hander
def vkm_value_handler(value: str):
values = []
if (d:=pcheck(VKM_RANGE_PATTERN, value)) is not None:
for i in range(d['ssid'], d['esid']):
values.append(f"vkm://{d['uid']}/{i}")
return values

# ! Plugin Class
class VKMusic(PluginBase):
def exist_token(self) -> bool:
if (s:=Service.parse_config()) is not None:
del s
return True
else:
return False

def on_init(self) -> None:
self.configurated = self.exist_token()

def on_run(self) -> None:
if self.configurated:
self.service = Service.parse_config()
else:
login, password = Prompt.ask("Login"), Prompt.ask("Password", password=True)
while not self.configurated:
tr = TokenReceiver(login, password)
if tr.auth(on_2fa=lambda: Prompt.ask("Code 2FA")):
tr.save_to_config()
self.configurated = True
else:
console.print("[red]Failed to get a token, repeat...[/red]")
self.service = Service.parse_config()
self.app.info(f"Service is worked: {repr(self.service)}")
# ! Registration
self.app.CODECS_KWARGS["vkm_service"] = self.service
self.add_value_handlers(vkm_value_handler)
self.add_codecs(VKMCodec)

# ! Registeration
__plugin__ = VKMusic
8 changes: 8 additions & 0 deletions plugins/VKMusic/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "VK Music",
"name_id": "seaplayer.plugins.vk.music",
"version": "0.2.0",
"author": "Romanin",
"description": "Music downloader from VK.",
"url": "https://github.com/romanin-rf/SeaPlayer"
}
2 changes: 2 additions & 0 deletions plugins/VKMusic/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
vkpymusic>=2.2.4
vbml>=1.1
14 changes: 14 additions & 0 deletions plugins/VKMusic/units.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from vbml import Pattern, Patcher
# > Typing
from typing import Optional, Dict, Any

# ! VBML Objects
PATCHER = Patcher()

VKM_MAIN_PATTERN = Pattern("vkm://<uid:int>/<sid:int>")
VKM_RANGE_PATTERN = Pattern("vkm://<uid:int>/<ssid:int>-<esid:int>")

# ! For VBML methods
def pcheck(pattern: Pattern, text: str) -> Optional[Dict[str, Any]]:
if isinstance(data:=PATCHER.check(pattern, text), dict):
return data
67 changes: 67 additions & 0 deletions plugins/VKMusic/vkmcodec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import asyncio
from vkpymusic import Service
from seaplayer.codecs.URLS import URLSoundCodec
from seaplayer.codecs.AnySound import AnySound
# > Typing
from typing import Optional, Tuple
# > Local Imports
from .units import PATCHER, VKM_MAIN_PATTERN

# ! Methods
def parse_vkm(url: str) -> Tuple[int, int]:
d = PATCHER.check(VKM_MAIN_PATTERN, url)
if not isinstance(d, dict):
raise RuntimeError("The values from the 'url' could not be parsed.")
return d.get("uid"), d.get("sid")

# ! Main Class
class VKMCodec(URLSoundCodec):
codec_name = "VKM"
codec_priority = 5.999

# ! Check Methods
@staticmethod
def is_this_codec(url: str) -> bool:
return isinstance(PATCHER.check(VKM_MAIN_PATTERN, url), dict)

@staticmethod
async def aio_is_this_codec(url: str) -> bool:
return isinstance(PATCHER.check(VKM_MAIN_PATTERN, url), dict)

# ! Main Functions
def __init__(
self,
url: str,
sound_device_id: Optional[int]=None,
vkm_service: Optional[Service]=None,
aio_init: bool=False,
**kwargs
) -> None:
if vkm_service is None:
raise RuntimeError("The 'service' argument is None.")
uid, sid = parse_vkm(url)
self.song = vkm_service.get_songs_by_userid(uid, 1, sid)[0]
self._title = (self.song.title if (len(self.song.title) > 0) else None) if isinstance(self.song.title, str) else None
self._artist = (self.song.artist if (len(self.song.artist) > 0) else None) if isinstance(self.song.artist, str) else None
super().__init__(self.song.url, sound_device_id, aio_init, **kwargs)
self.name = url

@staticmethod
async def __aio_init__(
url: str,
sound_device_id: Optional[int]=None,
vkm_service: Optional[Service]=None,
**kwargs
):
self = VKMCodec(url, sound_device_id, vkm_service, aio_init=True)
self._sound = await asyncio.to_thread(AnySound.from_url, self.song.url, device_id=sound_device_id)
return self

# ! Properys
@property
def title(self) -> str:
return self._title

@property
def artist(self) -> str:
return self._artist
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "SeaPlayer"
version = "0.8.2"
version = "0.8.3"
description = "SeaPlayer is a player that works in the terminal."
repository = "https://github.com/romanin-rf/SeaPlayer"
authors = ["Romanin <semina054@gmail.com>"]
@@ -57,9 +57,10 @@ typing-extensions = ">=4.6"
# Optional Modules
poetry = {version = ">=1.5", optional = true}
pyinstaller = {version = ">=5.11", optional = true}
wget = {version = ">=3.2", optional = true}

[tool.poetry.extras]
build = ["poetry", "pyinstaller"]
build = ["poetry", "pyinstaller", "wget"]

[build-system]
requires = ["poetry-core"]
1 change: 1 addition & 0 deletions seaplayer/codecs/Any.py
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@

class AnyCodec(CodecBase):
codec_name: str = "Any"
codec_priority: float=1024

# ! Initialized
def __init__(self, path: str, sound_device_id: Optional[int]=None, **kwargs) -> None:
1 change: 1 addition & 0 deletions seaplayer/codecs/FLAC.py
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@

class FLACCodec(AnyCodec):
codec_name: str = "FLAC"
codec_priority: float=4.0

# ! Testing
@staticmethod
1 change: 1 addition & 0 deletions seaplayer/codecs/MIDI.py
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
# ! Codec
class MIDICodec(AnyCodec):
codec_name: str = "MIDI"
codec_priority: float=5.0

# ! Testing
@staticmethod
1 change: 1 addition & 0 deletions seaplayer/codecs/MP3.py
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
# ! Main Class
class MP3Codec(AnyCodec):
codec_name: str = "MP3"
codec_priority: float=1.0

# ! Testing
@staticmethod
1 change: 1 addition & 0 deletions seaplayer/codecs/OGG.py
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@

class OGGCodec(AnyCodec):
codec_name: str = "OGG"
codec_priority: float=3.0

# ! Testing
@staticmethod
1 change: 1 addition & 0 deletions seaplayer/codecs/URLS.py
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
# ! Any URL Sound File
class URLSoundCodec(AnyCodec):
codec_name: str = "URLS"
codec_priority: float=6.0

# ! Codec Test
@staticmethod
1 change: 1 addition & 0 deletions seaplayer/codecs/WAV.py
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@

class WAVECodec(AnyCodec):
codec_name: str = "WAVE"
codec_priority: float=2.0

# ! Testing
@staticmethod
3 changes: 2 additions & 1 deletion seaplayer/codeсbase.py
Original file line number Diff line number Diff line change
@@ -7,7 +7,8 @@ def formater(**kwargs) -> str:
# ! Codecs Base
class CodecBase:
# * Codec Info
codec_name: str = "None"
codec_name: str="None"
codec_priority: float=0.0

# * Info
name: str
5 changes: 4 additions & 1 deletion seaplayer/plug/pluginbase.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
from textual.binding import Binding
from textual.screen import Screen
# > Typing
from typing import Optional, Generator, Type, Any
from typing import Optional, Generator, Type, Callable, List, Any
# > Local Import's
from ..codeсbase import CodecBase
from ..functions import formater
@@ -42,6 +42,9 @@ def install_screen(self, name: str, screen: Screen) -> None:
def add_codecs(self, *codecs: Type[CodecBase]) -> None:
self.app.CODECS += [ *codecs ]

def add_value_handlers(self, *handlers: Callable[[str], List[str]]) -> None:
self.pl.value_handlers += list(handlers)

# ! Dev Functions
def on_bindings(self) -> Generator[Binding, Any, None]:
yield None
3 changes: 2 additions & 1 deletion seaplayer/plug/pluginbase.pyi
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ from pydantic import BaseModel
from textual.screen import Screen
from textual.binding import Binding
# > Typing
from typing import Optional, Generator, Type, Any
from typing import Optional, Generator, Type, Callable, List, Any
# > Local Import's
from ..seaplayer import SeaPlayer
from ..codeсbase import CodecBase
@@ -28,6 +28,7 @@ class PluginBase:
# ! App Specific Methods
def install_screen(self, name: str, screen: Screen) -> None: ...
def add_codecs(self, *codecs: Type[CodecBase]) -> None: ...
def add_value_handlers(self, *handlers: Callable[[str], List[str]]) -> None: ...
def on_bindings(self) -> Generator[Binding, Any, None]: ...
# ! App On Methods
def on_init(self) -> None: ...
23 changes: 12 additions & 11 deletions seaplayer/plug/pluginloader.py
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
from importlib.util import spec_from_file_location, module_from_spec
# > Typing
from types import ModuleType
from typing import Optional, Dict, Union, Any, List, Tuple, Type, Generator
from typing import Optional, Dict, Union, Any, List, Tuple, Type, Generator, Callable
# > Local Import's
from .pipw import pip
from .pluginbase import PluginInfo, PluginBase
@@ -28,7 +28,7 @@

# ! Types
class PluginModuleType(ModuleType):
plugin_main: Type[PluginBase]
__plugin__: Type[PluginBase]

# ! Functions
def get_module_info(path: str):
@@ -56,7 +56,12 @@ def load_module(init_path: str) -> PluginModuleType:
return module

def plugin_from_module(app, pl, info: PluginInfo, module: PluginModuleType) -> PluginBase:
return module.plugin_main(app, pl, info)
return module.__plugin__(app, pl, info)

def load_plugin_info(path: str) -> PluginInfo:
with open(path, 'rb') as file:
data = file.read()
return PluginInfo.model_validate_json(data)

# ! Plugin Loader Config
class PluginLoaderConfigModel(BaseModel):
@@ -128,7 +133,7 @@ def enable_plugin_by_name_id(self, name_id: str) -> None:
# ! Plugin Loader Class
class PluginLoader:
__title__: str = "PluginLoader"
__version__: str = "0.3.0"
__version__: str = "0.4.0"
__author__: str = "Romanin"
__email__: str = "semina054@gmail.com"

@@ -154,6 +159,8 @@ def __init__(
self.on_plugins: List[PluginBase] = []
self.off_plugins: List[PluginInfo] = []
self.error_plugins: List[Tuple[str, str]] = []
# * Plugin Vars
self.value_handlers: List[Callable[[str], List[str]]] = []

# * Logging
self.app.info("---")
@@ -203,12 +210,6 @@ def search_plugins_paths():
if info_path is not None:
yield init_path, info_path, deps_path

@staticmethod
def load_plugin_info(path: str) -> PluginInfo:
with open(path, 'rb') as file:
data = file.read()
return PluginInfo.model_validate_json(data)

# ! On Init Method
def on_init(self) -> None:
self.app.info(f"{self.__title__} [#60fdff]v{self.__version__}[/#60fdff] from {self.__author__} ({self.__email__})", in_console=True)
@@ -218,7 +219,7 @@ def on_init(self) -> None:
for init_path, info_path, deps_path in plugins_paths:
info = None
try:
info = self.load_plugin_info(info_path)
info = load_plugin_info(info_path)
if not self.config.exists_plugin(info):
self.config.add_plugin(info)
self.app.info(f"{info.name} ({repr(info.name_id)}) > New plugin added to config!", in_console=True)
9 changes: 5 additions & 4 deletions seaplayer/plug/pluginloader.pyi
Original file line number Diff line number Diff line change
@@ -11,7 +11,8 @@ from typing import (
Tuple,
Union,
Optional,
Generator,
Callable,
Generator,
AsyncGenerator
)
# > Local Import's
@@ -30,6 +31,7 @@ DEPS_FILE_PATH = str
def get_module_info(path: str) -> Tuple[str, str]: ...
def load_module(path: str) -> PluginModuleType: ...
def plugin_from_module(app: SeaPlayer, pl: PluginLoader, info: PluginInfo, module: PluginModuleType) -> PluginBase: ...
def load_plugin_info(path: str) -> PluginInfo: ...

# ! Plugin Loader Config
class PluginLoaderConfigModel(BaseModel):
@@ -62,7 +64,8 @@ class PluginLoader:
on_plugins: List[PluginBase]
off_plugins: List[PluginInfo]
error_plugins: List[Tuple[str, str]]

value_handlers: List[Callable[[str], List[str]]]

def __init__(
self,
app: SeaPlayer,
@@ -77,8 +80,6 @@ class PluginLoader:
async def aio_search_plugins_paths() -> AsyncGenerator[Tuple[INIT_FILE_PATH, INFO_FILE_PATH, DEPS_FILE_PATH]]: ...
@staticmethod
def search_plugins_paths() -> Generator[Tuple[INIT_FILE_PATH, INFO_FILE_PATH, DEPS_FILE_PATH], Any, None]: ...
@staticmethod
def load_plugin_info(path: str) -> PluginInfo: ...

# ! App Specific Methods
def on_bindings(self) -> Generator[Binding, Any, None]: ...
8 changes: 6 additions & 2 deletions seaplayer/seaplayer.py
Original file line number Diff line number Diff line change
@@ -87,8 +87,8 @@ class SeaPlayer(App):
# ! SeaPlayer Configuration
cache = Cacher(CACHE_DIRPATH)
config = SeaPlayerConfig(CONFIG_FILEPATH)
image_type: Optional[Union[Type[AsyncImageLabel], Type[StandartImageLabel]]] = None
ll = LanguageLoader(LANGUAGES_DIRPATH, config.lang)
image_type: Optional[Union[Type[AsyncImageLabel], Type[StandartImageLabel]]] = None

# ! Bindings
BINDINGS = list(build_bindings(config, ll))
@@ -472,7 +472,10 @@ async def submit_plus_sound(self, value: str) -> None:
try:
self.last_handlered_values = glob.glob(value, recursive=self.config.recursive_search)
except:
self.last_handlered_values = [ value ]
self.last_handlered_values = []
if ENABLE_PLUGIN_SYSTEM:
for vhr in self.plugin_loader.value_handlers:
self.last_handlered_values += vhr(value)
if len(self.last_handlered_values) == 0:
self.last_handlered_values = [ value ]
self.info(f"Submit 'plus_sound' values: {repr(self.last_handlered_values)}")
@@ -530,5 +533,6 @@ async def action_quit(self):
def run(self, *args, **kwargs):
if ENABLE_PLUGIN_SYSTEM:
self.plugin_loader.on_run()
self.CODECS.sort(key=lambda x: x.codec_priority)
super().run(*args, **kwargs)

2 changes: 1 addition & 1 deletion seaplayer/units.py
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@

# ! Metadata
__title__ = "SeaPlayer"
__version__ = "0.8.2"
__version__ = "0.8.3"
__author__ = "Romanin"
__email__ = "semina054@gmail.com"
__url__ = "https://github.com/romanin-rf/SeaPlayer"

0 comments on commit 53bcf23

Please sign in to comment.