Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update 0.8.3 #41

Merged
merged 4 commits into from
Dec 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/",
Expand Down Expand Up @@ -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/",
Expand Down
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
2 changes: 1 addition & 1 deletion plugins/RichDiscordStatus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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>"]
Expand Down Expand Up @@ -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"]
Expand Down
1 change: 1 addition & 0 deletions seaplayer/codecs/Any.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions seaplayer/codecs/FLAC.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

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

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

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

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

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

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

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

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

# ! Testing
@staticmethod
Expand Down
3 changes: 2 additions & 1 deletion seaplayer/codeсbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion seaplayer/plug/pluginbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion seaplayer/plug/pluginbase.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: ...
Expand Down
23 changes: 12 additions & 11 deletions seaplayer/plug/pluginloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,7 +28,7 @@

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

# ! Functions
def get_module_info(path: str):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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"

Expand All @@ -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("---")
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
Loading