Skip to content

Commit

Permalink
Update 0.4a3
Browse files Browse the repository at this point in the history
- Fixed cleanup in builf.py
- Added new Nofy object (notifications)
- Slight fixes in exceptions.py
- Added some functions and parameters to LogMenu for convenience
- Disabling LogMenu now works fine
- Added more logging
  • Loading branch information
romanin-rf committed May 19, 2023
1 parent 8a403f3 commit 887e774
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 55 deletions.
21 changes: 9 additions & 12 deletions build.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
import shutil
import platform
import glob
from pathlib import Path
from typing import Dict, List

Expand Down Expand Up @@ -52,15 +52,12 @@ def add_datas(data: Dict[str, str]) -> List[str]: return [f"--add-data \"{locali
print(COMMAND, end="\n\n")
os.system(COMMAND)

for path in TRASH_FILES:
for i in TRASH_FILES:
path = Path(localize(i))
try:
path = Path(localize(path))
if path.is_file(): os.remove(path.name)
elif path.is_dir():
files = glob.glob(os.path.join(path.name, "**", "*"))
for filepath in files:
try: os.remove(filepath)
except: pass
try: os.removedirs(path.name)
except: pass
except: pass
if path.is_dir():
shutil.rmtree(path.name, ignore_errors=True)
else:
os.remove(path.name)
except:
pass
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "SeaPlayer"
version = "0.4a2"
version = "0.4a3"
description = "SeaPlayer is a player that works in the terminal."
repository = "https://github.com/romanin-rf/SeaPlayer"
authors = ["Romanin <semina054@gmail.com>"]
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ properties.py==1.1.0
playsoundsimple.py==0.6.3
pillow
aiofiles
pyinstaller
platformdirs
typing_inspect
typing_inspect
pyinstaller
11 changes: 11 additions & 0 deletions seaplayer/css/objects.css
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,15 @@

.log-menu.-hidden {
offset-y: 100%;
}

Nofy {
dock: top;
layer: nofys;
width: auto;
margin: 2 4;
padding: 1 2;
background: $background;
color: $text;
height: auto;
}
4 changes: 2 additions & 2 deletions seaplayer/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
class PathNotExistsError(Exception):
def __init__(self, path: str) -> None:
super().__init__()
self.args = (f"The element along the path '{path}' does not exist.",)
self.args = (f"The element along the path {repr(path)} does not exist.",)

class NotBooleanError(Exception):
def __init__(self, data: str) -> None:
super().__init__()
self.args = (f"The data '{data}' cannot be represented as a bool.",)
self.args = (f"The data {repr(data)} cannot be represented as a bool.",)
32 changes: 27 additions & 5 deletions seaplayer/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from rich.progress import Progress, BarColumn, TextColumn
from textual.widgets import Static, Label, ListItem, ListView, Input, TextLog
# > Typing
from typing import Optional, Tuple, TypeVar, Union, Any, Dict
from typing import Optional, Tuple, TypeVar, Union, Any, Literal
# > Local Import's
from .types import MusicList
from .codeсbase import CodecBase
Expand Down Expand Up @@ -297,14 +297,36 @@ def __init__(self, *children, **kwargs):

# ! Log Menu
class LogMenu(TextLog):
def __init__(self, enable_logging: bool=True, **kwargs):
def __init__(self, chap_max_width: int=8, enable_logging: bool=True, **kwargs):
self.enable_logging = enable_logging
self.chap_max_width = chap_max_width

if kwargs.get("classes", None) is not None:
kwargs["classes"] = kwargs["classes"] + " log-menu -hidden"
else:
kwargs["classes"] = "log-menu -hidden"

super().__init__(**kwargs)

def wlog(self, chap: str, msg: str, *, cc: str="green") -> None:
if self.enable_logging:
self.write(f"[[{cc}]{chap.center(8)}[/]]: {msg}\n")
def write_log(self, chap: str, msg: str, *, chap_color: str="green") -> None:
if self.enable_logging: self.write(f"[[{chap_color}]{chap.center(self.chap_max_width)}[/]]: {msg}", shrink=False)

def info(self, msg: str) -> None: self.write_log("INFO", msg, chap_color="green")
def error(self, msg: str) -> None: self.write_log("ERROR", msg, chap_color="red")
def warn(self, msg: str) -> None: self.write_log("WARN", msg, chap_color="orange")

# ! Nofy
class Nofy(Static):
def __init__(
self,
text: str,
life_time: float=3,
dosk: Literal["bottom", "left", "right", "top"]="top",
**kwargs
) -> None:
super().__init__(text, **kwargs)
self.life_time = life_time
self.styles.dock = dosk

def on_mount(self) -> None: self.set_timer(self.life_time, self.remove)
async def on_click(self) -> None: await self.remove()
25 changes: 24 additions & 1 deletion seaplayer/screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
# > Local Imports
from .types import Converter
from .modules.colorizer import richefication
from .objects import ConfigurateListView, ConfigurateListItem, InputField
from .objects import (
Nofy,
InputField,
ConfigurateListView,
ConfigurateListItem
)

# ! Constants
TEXT = b'CkFuIGVycm9yIGhhcyBvY2N1cnJlZC4gVG8gY29udGludWU6CgpQcmVzcyBFbnRlciB0byByZXR1cm4gdG8ge3N5c3RlbX0sIG9yCgpQcmVzcyBDVFJMK0FMVCtERUwgdG8gcmVzdGFydCB5b3VyIGNvbXB1dGVyLiBJZiB5b3UgZG8gdGhpcywKeW91IHdpbGwgbG9zZSBhbnkgdW5zYXZlZCBpbmZvcm1hdGlvbiBpbiBhbGwgb3BlbiBhcHBsaWNhdGlvbnMuCgpFcnJvcjogMEUgOiAwMTZGIDogQkZGOUIzRDQK'
Expand All @@ -34,6 +39,23 @@ class Configurate(Screen):
"""This Configurate Menu screen."""
BINDINGS = [("escape", "app.pop_screen", "Back")]

# ! Nofy Functions
def nofy(
self,
text: str,
life_time: float=3,
dosk: Literal["bottom", "left", "right", "top"]="top"
) -> None:
self.screen.mount(Nofy(text, life_time, dosk))

async def aio_nofy(
self,
text: str,
life_time: float=3,
dosk: Literal["bottom", "left", "right", "top"]="top"
) -> None:
await self.screen.mount(Nofy(text, life_time, dosk))

# ! Update Placeholder from InputField
def _upfif(self, attr_name: str) -> str:
return "Currect: " + str(eval(f"self.{attr_name}"))
Expand All @@ -43,6 +65,7 @@ def gupfif(self, attr_name: str):
# ! Update App Config
async def _uac(self, attr_name: str, input: InputField, value: str) -> None:
exec(f"self.{attr_name} = value")
await self.aio_nofy("Saved!")

def guac(self, attr_name: str):
async def an_uac(input: InputField, value: str) -> None: await self._uac(attr_name, input, value)
Expand Down
94 changes: 62 additions & 32 deletions seaplayer/seaplayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
get_sound_basename
)
from .objects import (
Nofy,
LogMenu,
InputField,
MusicListView,
Expand All @@ -37,7 +38,7 @@

# ! Metadata
__title__ = "SeaPlayer"
__version__ = "0.4a2"
__version__ = "0.4a3"
__author__ = "Romanin"
__email__ = "semina054@gmail.com"
__url__ = "https://github.com/romanin-rf/SeaPlayer"
Expand All @@ -51,12 +52,9 @@
CSS_LOCALDIR = os.path.join(os.path.dirname(__file__), "css")
ASSETS_DIRPATH = os.path.join(os.path.dirname(__file__), "assets")

CONFIG_DIRPATH = os.path.abspath(user_config_dir(__title__, __author__))
CONFIG_DIRPATH = user_config_dir(__title__, __author__, ensure_exists=True)
CONFIG_FILEPATH = os.path.join(CONFIG_DIRPATH, "config.properties")

# ! Initialized Directoryes
os.makedirs(CONFIG_DIRPATH, mode=644, exist_ok=True)

# ! Assets Paths
IMGPATH_IMAGE_NOT_FOUND = os.path.join(ASSETS_DIRPATH, "image-not-found.png")

Expand All @@ -70,6 +68,19 @@
"box": Resampling.BOX
}

# ! Main Functions
def build_bindings(config: SeaPlayerConfig):
yield Binding(config.key_quit, "quit", "Quit")
yield Binding("c,с", "push_screen('configurate')", "Configurate")
if config.log_menu_enable:
yield Binding("l,д", "app.toggle_class('.log-menu', '-hidden')", 'Logs')
yield Binding(config.key_rewind_back, "minus_rewind", f"Rewind -{config.rewind_count_seconds} sec")
yield Binding(config.key_rewind_forward, "plus_rewind", f"Rewind +{config.rewind_count_seconds} sec")
yield Binding(config.key_volume_down, "minus_volume", f"Volume -{round(config.volume_change_percent*100)}%")
yield Binding(config.key_volume_up, "plus_volume", f"Volume +{round(config.volume_change_percent*100)}%")
yield Binding("ctrl+s", "screenshot", "Screenshot")
yield Binding(UNKNOWN_OPEN_KEY, "push_screen('unknown')", "None", show=False)

# ! Main
class SeaPlayer(App):
# ! Textual Configuration
Expand All @@ -90,19 +101,7 @@ class SeaPlayer(App):
max_volume_percent: float = config.max_volume_percent

# ! Textual Keys Configuration
BINDINGS = [
Binding(config.key_quit, "quit", "Quit", priority=True),
Binding("c,с", "push_screen('configurate')", "Configurate", priority=True),
#Binding("l,д", "app.toggle_class('.log-menu', '-hidden')", 'Logs'),
Binding(config.key_rewind_back, "minus_rewind", f"Rewind -{config.rewind_count_seconds} sec"),
Binding(config.key_rewind_forward, "plus_rewind", f"Rewind +{config.rewind_count_seconds} sec"),
Binding(config.key_volume_down, "minus_volume", f"Volume -{round(config.volume_change_percent*100)}%"),
Binding(config.key_volume_up, "plus_volume", f"Volume +{round(config.volume_change_percent*100)}%"),
Binding("ctrl+s", "screenshot", "Screenshot"),
Binding(UNKNOWN_OPEN_KEY, "push_screen('unknown')", "None", show=False)
]
if config.log_menu_enable:
BINDINGS.append(Binding("l,д", "app.toggle_class('.log-menu', '-hidden')", 'Logs', priority=True))
BINDINGS = list(build_bindings(config))

# ! Template Configuration
currect_sound_uuid: Optional[str] = None
Expand All @@ -118,12 +117,36 @@ class SeaPlayer(App):
CODECS: List[Type[CodecBase]] = [ MP3Codec, WAVECodec, OGGCodec, MIDICodec ]
CODECS_KWARGS: Dict[str, Any] = {"sound_font_path": config.sound_font_path}

# ! Init Objects
log_menu = LogMenu(enable_logging=config.log_menu_enable, wrap=True, highlight=True, markup=True)

# ! Log Functions
info = log_menu.info
error = log_menu.error
warn = log_menu.warn

# ! Inherited Functions
async def action_push_screen(self, screen: str) -> None:
if self.SCREENS[screen].id != self.screen.id:
await super().action_push_screen(screen)

# ! Functions, Workers and other...
def nofy(
self,
text: str,
life_time: float=3,
dosk: Literal["bottom", "left", "right", "top"]="top"
) -> None:
self.screen.mount(Nofy(text, life_time, dosk))

async def aio_nofy(
self,
text: str,
life_time: float=3,
dosk: Literal["bottom", "left", "right", "top"]="top"
) -> None:
await self.screen.mount(Nofy(text, life_time, dosk))

def gcs(self) -> Optional[CodecBase]:
if (self.currect_sound is None) and (self.currect_sound_uuid is not None):
self.currect_sound = self.music_list_view.music_list.get(self.currect_sound_uuid)
Expand Down Expand Up @@ -166,37 +189,39 @@ async def update_loop_playback(self) -> None:
if (status == "Stoped") and (self.last_playback_status == "Playing"):
if self.playback_mode == 1:
sound.play()
self.log_menu.wlog("INFO", f"Replay sound: {repr(sound)}")
self.info(f"Replay sound: {repr(sound)}")
elif self.playback_mode == 2:
if (sound:=await self.set_sound_for_playback(sound_uuid:=await self.music_list_view.aio_get_next_sound_uuid(self.currect_sound_uuid), True)) is not None:
self.playback_mode_blocked = True
await self.music_list_view.aio_select_list_item_from_sound_uuid(sound_uuid)
sound.play()
self.log_menu.wlog("INFO", f"Playing the next sound: {repr(sound)}")
self.info(f"Playing the next sound: {repr(sound)}")

self.last_playback_status = status
await asyncio.sleep(0.33)

def compose(self) -> ComposeResult:
# * Other
self.log_menu = LogMenu(enable_logging=(self.config.log_menu_enable), wrap=True, highlight=True, markup=True)
self.log_menu.wlog("INFO", f"Codecs: {repr(self.CODECS)}")
self.info(f"{__title__} v{__version__} from {__author__} ({__email__})")
self.info(f"Source : {__url__}")
self.info(f"Codecs : {repr(self.CODECS)}")
self.info(f"Config Path : {repr(self.config.filepath)}")
self.info(f"CSS Dirpath : {repr(CSS_LOCALDIR)}")
self.info(f"Assets Dirpath : {repr(ASSETS_DIRPATH)}")

# * Play Screen
self.music_play_screen = Static(classes="screen-box")
self.music_play_screen.border_title = "Player"

img_image_not_found = Image.open(IMGPATH_IMAGE_NOT_FOUND)

# * Image Object Init
self.music_selected_label = Label(self.get_sound_selected_label_text(), classes="music-selected-label")
if self.config.image_update_method == "sync":
self.music_image = StandartImageLabel(img_image_not_found, resample=RESAMPLING_SAFE[self.config.image_resample_method])
self.music_image = StandartImageLabel(Image.open(IMGPATH_IMAGE_NOT_FOUND), resample=RESAMPLING_SAFE[self.config.image_resample_method])
elif self.config.image_update_method == "async":
self.music_image = AsyncImageLabel(img_image_not_found, resample=RESAMPLING_SAFE[self.config.image_resample_method])
self.music_image = AsyncImageLabel(Image.open(IMGPATH_IMAGE_NOT_FOUND), resample=RESAMPLING_SAFE[self.config.image_resample_method])
else:
raise RuntimeError("The configuration 'image_update_method' is incorrect.")

self.log_menu.wlog("INFO", f"The picture from the media file is rendered using the {repr(self.config.image_update_method)} method.")
self.info(f"The picture from the media file is rendered using the {repr(self.config.image_update_method)} method.")

# * Compositions Screen
self.music_list_screen = Static(classes="screen-box")
Expand Down Expand Up @@ -237,6 +262,7 @@ async def _spm(input: InputField, value: Any) -> None: await self.submit_plus_so
)

async def add_sounds_to_list(self) -> None:
added_oks = 0
async for path in aiter(self.last_paths_globalized):
async for codec in aiter(self.CODECS):
if await codec.aio_is_this_codec(path):
Expand All @@ -245,10 +271,13 @@ async def add_sounds_to_list(self) -> None:
if sound is not None:
if not await self.music_list_view.music_list.aio_exists_sha1(sound):
await self.music_list_view.aio_add_sound(sound)
self.log_menu.wlog("INFO", f"Song added: {repr(sound)}")
self.info(f"Song added: {repr(sound)}")
added_oks += 1
break
if sound is None:
self.log_menu.wlog("ERROR", f"The sound could not be loaded: {repr(path)}", cc="red")
self.error(f"The sound could not be loaded: {repr(path)}")
self.info(f"Added [cyan]{added_oks}[/cyan] songs!")
await self.aio_nofy(f"Added [cyan]{added_oks}[/cyan] songs!")

async def currect_sound_stop(self, sound: Optional[CodecBase]=None):
if sound is None: sound = await self.aio_gcs()
Expand Down Expand Up @@ -324,7 +353,7 @@ async def set_sound_for_playback(
if (sound:=self.music_list_view.get_sound(self.currect_sound_uuid)) is not None:
sound.set_volume(self.currect_volume)
await self.music_image.update_image(image_from_bytes(sound.icon_data))
self.log_menu.wlog("INFO", f"A new sound has been selected: {repr(sound)}")
self.info(f"A new sound has been selected: {repr(sound)}")
self.currect_sound = sound
self.music_selected_label.update(await self.aio_get_sound_selected_label_text())
return sound
Expand Down Expand Up @@ -355,7 +384,8 @@ async def action_minus_volume(self) -> None:

async def action_screenshot(self) -> None:
path = self.save_screenshot(path=LOCALDIR)
self.log_menu.wlog("INFO", f"Screenshot saved: {repr(path)}")
self.info(f"Screenshot saved to: {repr(path)}")
await self.aio_nofy(f"Screenshot saved to: [green]{repr(path)}[/]")

async def action_quit(self):
self.started = False
Expand Down

0 comments on commit 887e774

Please sign in to comment.