Skip to content

Commit 2f31bcd

Browse files
author
paranoik1
committed
change mode in tui
1 parent 4186be6 commit 2f31bcd

5 files changed

Lines changed: 47 additions & 26 deletions

File tree

image.png

863 Bytes
Loading

src/msoc/__main__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import argparse
22
import asyncio
33

4-
from .core import Mode, search
4+
from .core import SearchMode, search
55

66

7-
async def main(query: str, mode: Mode) -> None:
7+
async def main(query: str, mode: SearchMode) -> None:
88
async for sound in search(query, mode=mode):
99
print(
1010
f"Name: {sound.title}\n"
@@ -34,15 +34,15 @@ def execute() -> None:
3434
help="Режим поиска: fast (только первые страницы) или full (все страницы)",
3535
)
3636
args = parser.parse_args()
37+
mode = SearchMode(args.mode)
3738

3839
if args.tui:
3940
from .textual_app import MsocApp
4041

41-
app = MsocApp()
42+
app = MsocApp(search_mode=mode)
4243
app.run()
4344
return
4445

45-
mode = Mode(args.mode)
4646
query = args.query if args.query else input("Запрос: ")
4747
asyncio.run(main(query, mode))
4848

src/msoc/core.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from .sound import Sound
1111

1212
__all__ = [
13-
"Mode",
13+
"SearchMode",
1414
"search",
1515
"get_engines",
1616
"register_engine",
@@ -20,7 +20,7 @@
2020
]
2121

2222

23-
class Mode(Enum):
23+
class SearchMode(Enum):
2424
Fast = "fast"
2525
Full = "full"
2626

@@ -86,7 +86,7 @@ def unload_search_engine(name: str) -> None:
8686
logger.info("Удалён движок: %s", name)
8787

8888

89-
async def search(query: str, mode: Mode = Mode.Fast) -> AsyncGenerator[Sound, None]:
89+
async def search(query: str, mode: SearchMode = SearchMode.Fast) -> AsyncGenerator[Sound, None]:
9090
"""
9191
Запускает параллельный поиск музыки по всем зарегистрированным движкам.
9292
@@ -95,17 +95,19 @@ async def search(query: str, mode: Mode = Mode.Fast) -> AsyncGenerator[Sound, No
9595
9696
Args:
9797
query: Поисковый запрос (название трека, исполнитель или и то и другое).
98-
mode: Режим поиска — ``Fast`` (только первые страницы) или ``Full``
99-
(все страницы через ``search_full``, если он реализован).
98+
99+
mode: Режим поиска — Fast (только первые страницы) или Full
100+
(все страницы через search_full, если он реализован).
100101
101102
Yields:
102103
Sound — унифицированная информация о найденном треке.
103-
Поле ``_engine`` автоматически заполняется именем движка-источника.
104+
105+
Поле _engine автоматически заполняется именем движка-источника.
104106
105107
Note:
106108
Если ни один движок не зарегистрирован, генератор немедленно завершится.
107-
Ошибки отдельных движков логируются на уровне CRITICAL и не прерывают
108-
поиск остальными движками.
109+
110+
Ошибки отдельных движков логируются и не прерывают поиск остальными движками.
109111
"""
110112
engines = get_engines()
111113
queue: asyncio.Queue[Sound | None] = asyncio.Queue()
@@ -156,7 +158,7 @@ async def _engine_search(
156158
)
157159
continue
158160

159-
if mode is Mode.Full:
161+
if mode is SearchMode.Full:
160162
search_full = getattr(engine_module, "search_full", None)
161163
if search_full is not None:
162164
callback = search_full

src/msoc/textual_app.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import re
23
import queue
34
import string
45
import subprocess
@@ -10,15 +11,17 @@
1011
from functools import cached_property, cache
1112
from enum import Enum
1213
import sounddevice as sd # type: ignore[import-untyped]
13-
from textual.app import App, ComposeResult
14+
from textual.app import App, ComposeResult, Binding
1415
from textual.widgets import Header, Footer, Input, Button, Label
16+
from textual.reactive import reactive
1517
from textual.containers import VerticalScroll, VerticalGroup, HorizontalGroup
1618

17-
from msoc import Sound, search
19+
from msoc import Sound, search, SearchMode
1820

1921

2022
logger = logging.getLogger()
2123

24+
2225
Channels = int
2326
SampleRate = float
2427
Duration = float
@@ -130,9 +133,6 @@ def _stream_play_ffmpeg(self, sound_filepath: str, channels: Channels, samplerat
130133
ffmpeg_cmd = [
131134
"ffmpeg",
132135
"-y", # Перезаписывать sound_filepath без вопросов
133-
# "-reconnect", "1",
134-
# "-reconnect_streamed", "1",
135-
# "-reconnect_delay_max", "2",
136136
"-i", sound_filepath,
137137
# для sounddeivce (numpy тип)
138138
"-f", "f32le", "-acodec", "pcm_f32le", "-ac", str(channels), "-ar", str(samplerate), "pipe:1",
@@ -469,26 +469,44 @@ class MsocApp(App):
469469
}
470470
"""
471471

472-
def __init__(self):
472+
suffix_title_pattern = re.compile(r' \([a-zA-Z]* mode\)$')
473+
474+
BINDINGS = [
475+
Binding('m', 'toggle_search_mode', "Change Search Mode")
476+
]
477+
478+
def __init__(self, search_mode: SearchMode = SearchMode.Fast):
473479
super().__init__()
474480
self.player = AudioPlayer(self)
481+
self.search_mode = search_mode
482+
self.update_title()
483+
484+
def update_title(self, title: str | None = None):
485+
if not title:
486+
title = self.suffix_title_pattern.sub('', self.title)
487+
488+
self.title = title + f' ({self.search_mode.value.title()} mode)'
489+
490+
def action_toggle_search_mode(self):
491+
self.search_mode = SearchMode.Fast if self.search_mode == SearchMode.Full else SearchMode.Full
492+
self.update_title()
475493

476494
def on_input_submitted(self, event: Input.Submitted):
477495
if event.control.id != "search":
478496
return
479-
self.title = "Идет поиск..."
497+
self.update_title("Идет поиск...")
480498
event.control.disabled = True
481499
self.run_worker(self.search_task(event.value), name="search")
482500

483501
async def search_task(self, query: str):
484502
list_sounds_container = self.query_one("#list-sounds")
485503
list_sounds_container.remove_children()
486504

487-
async for sound in search(query):
505+
async for sound in search(query, mode=self.search_mode):
488506
sound_widget = SoundWidget(sound, self.player)
489507
await list_sounds_container.mount(sound_widget)
490508

491-
self.title = "Поиск завершен"
509+
self.update_title("Поиск завершен")
492510
self.query_one("#search").disabled = False
493511

494512
def compose(self) -> ComposeResult:

src/msoc/utils.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33

44
logger = logging.getLogger("msoc")
55

6+
http_pattern = re.compile(r'^https?://')
7+
68

79
def validate_and_return_absolute_url(sound_url: str, base_url: str) -> str:
810
"""
911
Преобразует относительный URL в абсолютный на основе base_url.
1012
1113
Args:
1214
sound_url: Относительный или абсолютный URL трека.
13-
15+
1416
base_url: Базовый URL сайта для преобразования относительных ссылок.
1517
1618
Returns:
@@ -20,11 +22,10 @@ def validate_and_return_absolute_url(sound_url: str, base_url: str) -> str:
2022
ValueError: Если base_url не является корректным абсолютным URL.
2123
"""
2224

23-
pattern = re.compile(r'^https?://')
24-
if pattern.search(sound_url):
25+
if http_pattern.search(sound_url):
2526
return sound_url
2627

27-
if not pattern.search(base_url):
28+
if not http_pattern.search(base_url):
2829
raise ValueError('Базовый url должен быть абсолютным')
2930

3031
url_fragments = base_url.split('/')

0 commit comments

Comments
 (0)