diff --git a/build.py b/build.py index 3d6c835..ce1789e 100644 --- a/build.py +++ b/build.py @@ -4,9 +4,12 @@ from pathlib import Path from typing import Dict, List -if platform.system() == "Windows": ds = ";" -elif platform.system() == "Linux": ds = ":" -else: raise RuntimeError("Your operating system is not supported.") +if platform.system() == "Windows": + ds = ";" +elif platform.system() == "Linux": + ds = ":" +else: + raise RuntimeError("Your operating system is not supported.") LOCALDIR = os.getcwd() TRASH_FILES = [ "build", "SeaPlayer.spec" ] @@ -68,8 +71,11 @@ "seaplayer/assets/image-not-found.png": "seaplayer/assets/" } -def localize(path: str) -> str: return os.path.join(LOCALDIR, path.replace('/', os.sep).replace('\\', os.sep)) -def add_datas(data: Dict[str, str]) -> List[str]: return [f"--add-data \"{localize(path)}{ds}{data[path]}\"" for path in data] +def localize(path: str) -> str: + return os.path.join(LOCALDIR, path.replace('/', os.sep).replace('\\', os.sep)) + +def add_datas(data: Dict[str, str]) -> List[str]: + return [f"--add-data \"{localize(path)}{ds}{data[path]}\"" for path in data] COMMAND_LINE = [ "pyinstaller", "--noconfirm", "--console", "--clean", "--onefile", diff --git a/pyproject.toml b/pyproject.toml index d7cc573..9e4b164 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "SeaPlayer" -version = "0.5.8" +version = "0.6.0" description = "SeaPlayer is a player that works in the terminal." repository = "https://github.com/romanin-rf/SeaPlayer" authors = ["Romanin "] diff --git a/seaplayer/screens/Configurate.py b/seaplayer/screens/Configurate.py index 3595683..e569079 100644 --- a/seaplayer/screens/Configurate.py +++ b/seaplayer/screens/Configurate.py @@ -78,12 +78,14 @@ async def aio_nofy( # ! Update Placeholder from InputField def _upfif(self, attr_name: str) -> str: return "Currect: " + str(eval(f"self.{attr_name}")) + def gupfif(self, attr_name: str): return lambda: self._upfif(attr_name) # ! Update App Config async def _uac(self, attr_name: str, input: InputField, value: str) -> None: exec(f"self.{attr_name} = value") + self.app.update_bindings() await self.aio_nofy("Saved!") if INIT_SOUNDDEVICE: @@ -94,7 +96,8 @@ async def n_ucsdi(option: DataOption) -> None: return n_ucsdi def guac(self, attr_name: str): - async def an_uac(input: InputField, value: str) -> None: await self._uac(attr_name, input, value) + async def an_uac(input: InputField, value: str) -> None: + await self._uac(attr_name, input, value) return an_uac # ! Configurator Generators @@ -114,7 +117,7 @@ def create_configurator_type( submit=self.guac(attr_name), update_placeholder=self.gupfif(attr_name) ), - title="[red]{"+group+"}[/red]: "+title+f" ({richefication(type_alias)})", + title="[red]{"+group+"}[/red]: "+f"{title} ({richefication(type_alias)})", desc=desc+(" [red](restart required)[/red]" if restart_required else ""), height=5 ) @@ -131,7 +134,7 @@ def create_configurator_keys( submit=self.guac(attr_name), update_placeholder=self.gupfif(attr_name) ), - title="[red]{Key}[/red]: "+title+f" ({richefication(str)})", + title="[red]{Key}[/red]: "+f"{title} ({richefication(str)})", desc=desc+(" [red](restart required)[/red]" if restart_required else ""), height=5 ) @@ -179,19 +182,19 @@ def compose(self) -> ComposeResult: "app.config.volume_change_percent", "Playback", "Volume Change Percent", "Percentage by which the volume changes when the special keys are pressed.", - float, float + float, float, False ) yield self.create_configurator_type( "app.config.rewind_count_seconds", "Playback", "Rewind Count Seconds", "The value of the seconds by which the current sound will be rewound.", - int, int + int, int, False ) yield self.create_configurator_type( "app.config.max_volume_percent", "Playback", "Max Volume Percent", "Maximum volume value.", - float, float + float, float, False ) yield self.create_configurator_type( "app.config.recursive_search", @@ -203,11 +206,11 @@ def compose(self) -> ComposeResult: "app.config.log_menu_enable", "Debag", "Log Menu Enable", "Menu with logs for the current session.", - conv.boolean, bool + conv.boolean, bool, False ) - yield self.create_configurator_keys("app.config.key_quit", "Quit", "Сlose the app.") - yield self.create_configurator_keys("app.config.key_rewind_forward", "Rewind Forward", "Forwards rewinding.") - yield self.create_configurator_keys("app.config.key_rewind_back", "Rewind Back", "Backwards rewinding.") - yield self.create_configurator_keys("app.config.key_volume_up", "Volume +", "Turn up the volume.") - yield self.create_configurator_keys("app.config.key_volume_down", "Volume -", "Turn down the volume.") + yield self.create_configurator_keys("app.config.key_quit", "Quit", "Сlose the app.", False) + yield self.create_configurator_keys("app.config.key_rewind_forward", "Rewind Forward", "Forwards rewinding.", False) + yield self.create_configurator_keys("app.config.key_rewind_back", "Rewind Back", "Backwards rewinding.", False) + yield self.create_configurator_keys("app.config.key_volume_up", "Volume +", "Turn up the volume.", False) + yield self.create_configurator_keys("app.config.key_volume_down", "Volume -", "Turn down the volume.", False) yield Footer() diff --git a/seaplayer/seaplayer.py b/seaplayer/seaplayer.py index 7a738a9..2b3625a 100644 --- a/seaplayer/seaplayer.py +++ b/seaplayer/seaplayer.py @@ -3,7 +3,7 @@ import asyncio # > Graphics from textual import on -from textual.binding import Binding +from textual.binding import Binding, _Bindings from textual.app import App, ComposeResult from textual.containers import Horizontal, Vertical from textual.widgets import Header, Footer, Static, Label, Button @@ -53,21 +53,21 @@ if ENABLE_PLUGIN_SYSTEM: from .plug import PluginLoader +# ! 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): - # ! Pre-Initialization 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) - # ! Textual Configuration TITLE = f"{__title__} v{__version__}" CSS_PATH = [ @@ -84,7 +84,6 @@ def build_bindings(config: SeaPlayerConfig): # ! SeaPlayer Configuration cache = Cacher(CACHE_DIRPATH) config = SeaPlayerConfig(CONFIG_FILEPATH) - max_volume_percent: float = config.max_volume_percent image_type: Optional[Union[Type[AsyncImageLabel], Type[StandartImageLabel]]] = None # ! Textual Keys Configuration @@ -126,6 +125,9 @@ def __init__(self, *args, **kwargs) -> None: self.plugin_loader = PluginLoader(self) self.plugin_loader.on_init() + def update_bindings(self) -> None: + self._bindings = _Bindings(list(build_bindings(self.config))) + # ! Inherited Functions async def action_push_screen(self, screen: str) -> None: if self.SCREENS[screen].id != self.screen.id: @@ -473,7 +475,7 @@ async def action_minus_rewind(self): async def action_plus_volume(self) -> None: if (sound:=await self.aio_gcs()) is not None: - if (vol:=round(sound.get_volume()+self.config.volume_change_percent, 2)) <= self.max_volume_percent: + if (vol:=round(sound.get_volume()+self.config.volume_change_percent, 2)) <= self.config.max_volume_percent: self.currect_volume = vol sound.set_volume(vol) diff --git a/seaplayer/types/Cache.py b/seaplayer/types/Cache.py index d9e862b..6af06b9 100644 --- a/seaplayer/types/Cache.py +++ b/seaplayer/types/Cache.py @@ -36,6 +36,8 @@ def write_var(self, value: Any, name: str, *, group: str="main") -> None: self.w def read_var(self, name: str, default: D, *, group: str="main") -> D: return self.read(os.path.join(self.vars_dirpath, f"{name}-{group}.pycache"), default) def var(self, name: str, default: D, *, group: str="main") -> D: - def setter(s, value: D) -> None: self.write_var(value, name, group=group) - def getter(s) -> D: return self.read_var(name, default, group=group) + def setter(s, value: D) -> None: + self.write_var(value, name, group=group) + def getter(s) -> D: + return self.read_var(name, default, group=group) return property(getter, setter) diff --git a/seaplayer/types/Convert.py b/seaplayer/types/Convert.py index 889e922..f890fed 100644 --- a/seaplayer/types/Convert.py +++ b/seaplayer/types/Convert.py @@ -14,13 +14,17 @@ def __init__(self, *args, **kwargs) -> None: @staticmethod def conv(tp: type, value: str) -> Tuple[bool, Optional[Any]]: - try: return True, tp(value) - except: return False, None + try: + return True, tp(value) + except: + return False, None @staticmethod async def aio_conv(tp: type, value: str) -> Tuple[bool, Optional[Any]]: - try: return True, tp(value) - except: return False, None + try: + return True, tp(value) + except: + return False, None def gen_conv(self, tp: type): def conv_wrapper(value: str) -> Tuple[bool, Optional[Any]]: @@ -36,22 +40,27 @@ async def aio_conv_wrapper(value: str) -> Tuple[bool, Optional[Any]]: @staticmethod def path(value: str) -> str: """Checking the existence of a `path`.""" - if not Path(value).exists(): raise PathNotExistsError(value) + if not Path(value).exists(): + raise PathNotExistsError(value) return value @staticmethod def filepath(value: str) -> str: """Check if there is a file on the path.""" path = Path(value) - if not(path.exists() and path.is_file()): raise PathNotExistsError(value) + if not(path.exists() and path.is_file()): + raise PathNotExistsError(value) return value @staticmethod def boolean(value: str) -> bool: """Converting to `bool`.""" - if value.lower() == "true": return True - elif value.lower() == "false": return False - else: raise NotBooleanError(value) + if value.lower() == "true": + return True + elif value.lower() == "false": + return False + else: + raise NotBooleanError(value) @staticmethod def optional(tp: type): @@ -65,14 +74,17 @@ def optional_wrapper(value: str): def union(*tps: type): def union_wrapper(value: str): for tp in tps: - try: return tp(value) - except: pass + try: + return tp(value) + except: + pass raise TypeError(f"Could not convert to any of the listed types: {tps}") return union_wrapper @staticmethod def literal_string(*values: str): def literal_string_wrapper(value: str): - if value in values: return value + if value in values: + return value raise RuntimeError(f"The value ({repr(value)}) is not in the list of values.") return literal_string_wrapper \ No newline at end of file diff --git a/seaplayer/types/MusicList.py b/seaplayer/types/MusicList.py index 5ea747b..67d4207 100644 --- a/seaplayer/types/MusicList.py +++ b/seaplayer/types/MusicList.py @@ -6,10 +6,12 @@ # ! Main Class class MusicList: @staticmethod - def get_file_sha1(sound: CodecBase, buffer_size: int=65536) -> str: return sound.__sha1__(buffer_size) + def get_file_sha1(sound: CodecBase, buffer_size: int=65536) -> str: + return sound.__sha1__(buffer_size) @staticmethod - async def aio_get_file_sha1(sound: CodecBase, buffer_size: int=65536) -> str: return await sound.__aio_sha1__(buffer_size) + async def aio_get_file_sha1(sound: CodecBase, buffer_size: int=65536) -> str: + return await sound.__aio_sha1__(buffer_size) def __init__(self, **child_sounds: CodecBase) -> None: self.sounds: Dict[str, CodecBase] = {} @@ -17,18 +19,28 @@ def __init__(self, **child_sounds: CodecBase) -> None: if isinstance(child_sounds[key], CodecBase): self.sounds[key] = child_sounds[key] - def exists(self, sound_uuid: str) -> bool: return sound_uuid in self.sounds.keys() - def get(self, sound_uuid: str) -> Optional[CodecBase]: return self.sounds.get(sound_uuid) + def exists(self, sound_uuid: str) -> bool: + return sound_uuid in self.sounds.keys() + + def get(self, sound_uuid: str) -> Optional[CodecBase]: + return self.sounds.get(sound_uuid) + def add(self, sound: CodecBase) -> str: self.sounds[(sound_uuid:=self.get_file_sha1(sound))] = sound return sound_uuid + def exists_sha1(self, sound: CodecBase) -> bool: return self.get_file_sha1(sound) in self.sounds.keys() - async def aio_exists(self, sound_uuid: str): return sound_uuid in self.sounds.keys() - async def aio_get(self, sound_uuid: str): return self.sounds.get(sound_uuid) + async def aio_exists(self, sound_uuid: str): + return sound_uuid in self.sounds.keys() + + async def aio_get(self, sound_uuid: str): + return self.sounds.get(sound_uuid) + async def aio_add(self, sound: CodecBase): self.sounds[(sound_uuid:=await self.aio_get_file_sha1(sound))] = sound return sound_uuid + async def aio_exists_sha1(self, sound: CodecBase) -> bool: return await self.aio_get_file_sha1(sound) in self.sounds.keys()