tidalamp 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tidalamp/__init__.py +17 -0
- tidalamp/about.py +202 -0
- tidalamp/app.py +1545 -0
- tidalamp/artwork.py +365 -0
- tidalamp/audio.py +205 -0
- tidalamp/auth.py +149 -0
- tidalamp/cli.py +138 -0
- tidalamp/columns.py +87 -0
- tidalamp/config.py +268 -0
- tidalamp/i18n.py +592 -0
- tidalamp/library.py +430 -0
- tidalamp/lyrics.py +112 -0
- tidalamp/mpris.py +409 -0
- tidalamp/net.py +47 -0
- tidalamp/player.py +254 -0
- tidalamp/queue.py +378 -0
- tidalamp/screens.py +1385 -0
- tidalamp/settings.py +101 -0
- tidalamp/spectrum.py +124 -0
- tidalamp/stream.py +200 -0
- tidalamp/theme.py +283 -0
- tidalamp/widgets.py +468 -0
- tidalamp/winamp.tcss +596 -0
- tidalamp-0.1.0.dist-info/METADATA +693 -0
- tidalamp-0.1.0.dist-info/RECORD +28 -0
- tidalamp-0.1.0.dist-info/WHEEL +4 -0
- tidalamp-0.1.0.dist-info/entry_points.txt +2 -0
- tidalamp-0.1.0.dist-info/licenses/LICENSE +674 -0
tidalamp/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""tidalamp: a Winamp-flavoured TIDAL client for the terminal.
|
|
2
|
+
|
|
3
|
+
Copyright (C) 2026 wh01s17.
|
|
4
|
+
|
|
5
|
+
Free software under the GNU General Public License, version 3 or later; see
|
|
6
|
+
the LICENSE file. No warranty of any kind. Not affiliated with TIDAL, Aspiro,
|
|
7
|
+
or the Winamp trademark holders.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
|
|
14
|
+
# Without a handler of our own, logging's last-resort handler would print
|
|
15
|
+
# warnings straight to stderr — on top of the TUI. config.setup_logging()
|
|
16
|
+
# adds a real file handler when TIDALAMP_DEBUG is set.
|
|
17
|
+
logging.getLogger("tidalamp").addHandler(logging.NullHandler())
|
tidalamp/about.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Who wrote this, under what licence, and what changed in each version.
|
|
2
|
+
|
|
3
|
+
The release notes live here as data rather than being parsed out of
|
|
4
|
+
`CHANGELOG.md` at runtime: that file is not shipped inside the wheel, so a
|
|
5
|
+
help screen that read it would work from a git checkout and be empty for
|
|
6
|
+
everyone who installed the package — which is everyone the screen is for.
|
|
7
|
+
|
|
8
|
+
Nothing here imports Textual, so it stays testable without an app.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
|
|
15
|
+
from . import __version__
|
|
16
|
+
from .i18n import _
|
|
17
|
+
|
|
18
|
+
REPO_URL = "https://github.com/wh01s17/tidalamp"
|
|
19
|
+
AUTHOR = "wh01s17"
|
|
20
|
+
LICENSE = "GPL-3.0-or-later"
|
|
21
|
+
LICENSE_URL = "https://www.gnu.org/licenses/gpl-3.0.html"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def version() -> str:
|
|
25
|
+
"""The running version. Installed metadata wins over the source constant.
|
|
26
|
+
|
|
27
|
+
They agree in a release — `release.yml` refuses a tag that disagrees with
|
|
28
|
+
`pyproject.toml` — but an editable install of a working tree can be ahead
|
|
29
|
+
of the string in `__init__.py`.
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
from importlib.metadata import PackageNotFoundError
|
|
33
|
+
from importlib.metadata import version as installed
|
|
34
|
+
|
|
35
|
+
return installed("tidalamp")
|
|
36
|
+
except (ImportError, PackageNotFoundError):
|
|
37
|
+
return __version__
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class Release:
|
|
42
|
+
"""One version and what it brought, short enough to read on one screen."""
|
|
43
|
+
|
|
44
|
+
version: str
|
|
45
|
+
date: str
|
|
46
|
+
changes: tuple[str, ...] = field(default_factory=tuple)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def releases() -> tuple[Release, ...]:
|
|
50
|
+
"""Newest first. Translated at call time, not at import time."""
|
|
51
|
+
return (
|
|
52
|
+
Release(
|
|
53
|
+
"0.1.0",
|
|
54
|
+
"2026-09-09",
|
|
55
|
+
(
|
|
56
|
+
_("Interfaz Winamp 2.x: reloj, marquesina, analizador y playlist."),
|
|
57
|
+
_("Reproducción con mpv por IPC, hasta FLAC 24 bit/96 kHz."),
|
|
58
|
+
_("Búsqueda, biblioteca paginada, favoritos y cola persistente."),
|
|
59
|
+
_("Menú de pista: ahora, a continuación, radio y favoritos."),
|
|
60
|
+
_("Ventana de configuración, con los ritmos hi-res de PipeWire."),
|
|
61
|
+
_("Letras sincronizadas, ecualizador de 10 bandas y balance."),
|
|
62
|
+
_("Carátula en kitty, sixel o medios bloques, y espectro con cava."),
|
|
63
|
+
_("MPRIS2 completo, incluida la lista de pistas."),
|
|
64
|
+
_("Arranque directo con tidalamp; interfaz bilingüe según el locale."),
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Textual's key names are meant for the config file, not for reading: the
|
|
71
|
+
# screen shows the character you actually press.
|
|
72
|
+
_KEY_NAMES: dict[str, str] = {
|
|
73
|
+
"slash": "/",
|
|
74
|
+
"backslash": "\\",
|
|
75
|
+
"comma": ",",
|
|
76
|
+
"full_stop": ".",
|
|
77
|
+
"plus": "+",
|
|
78
|
+
"minus": "-",
|
|
79
|
+
"equals_sign": "=",
|
|
80
|
+
"question_mark": "?",
|
|
81
|
+
"up": "↑",
|
|
82
|
+
"down": "↓",
|
|
83
|
+
"left": "←",
|
|
84
|
+
"right": "→",
|
|
85
|
+
"enter": "↵",
|
|
86
|
+
"escape": "esc",
|
|
87
|
+
"pageup": "pgup",
|
|
88
|
+
"pagedown": "pgdn",
|
|
89
|
+
"delete": "del",
|
|
90
|
+
"space": "␣",
|
|
91
|
+
"alt+up": "alt+↑",
|
|
92
|
+
"alt+down": "alt+↓",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def pretty_keys(binding: str) -> str:
|
|
97
|
+
"""«slash» -> «/», «d,delete» -> «d / del». One binding, as you type it."""
|
|
98
|
+
parts = [key.strip() for key in binding.split(",") if key.strip()]
|
|
99
|
+
return " / ".join(_KEY_NAMES.get(key, key) for key in parts)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class Section:
|
|
104
|
+
"""A titled block of the help screen: what to press, and what it does."""
|
|
105
|
+
|
|
106
|
+
title: str
|
|
107
|
+
rows: tuple[tuple[str, str], ...]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def shortcuts(keys) -> tuple[Section, ...]:
|
|
111
|
+
"""The whole key map, grouped for reading.
|
|
112
|
+
|
|
113
|
+
``keys`` resolves an action to its effective binding — the config file's
|
|
114
|
+
if the user rebound it — so what the screen shows is what the app answers
|
|
115
|
+
to. Keys given as literals here are the ones `DEFAULT_KEYS` deliberately
|
|
116
|
+
leaves unbindable, because a typo in them locks you out of the browser.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def key(action: str) -> str:
|
|
120
|
+
return pretty_keys(keys(action))
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
Section(
|
|
124
|
+
_("Reproducción"),
|
|
125
|
+
(
|
|
126
|
+
(key("play"), _("reproducir o pausar (▶ / ‖)")),
|
|
127
|
+
(key("stop"), _("detener")),
|
|
128
|
+
(key("prev"), _("pista anterior")),
|
|
129
|
+
(key("next"), _("pista siguiente")),
|
|
130
|
+
(key("seek_back"), _("retroceder 5 s")),
|
|
131
|
+
(key("seek_fwd"), _("avanzar 5 s")),
|
|
132
|
+
(key("toggle_time"), _("tiempo transcurrido o restante")),
|
|
133
|
+
),
|
|
134
|
+
),
|
|
135
|
+
Section(
|
|
136
|
+
_("Volumen y sonido"),
|
|
137
|
+
(
|
|
138
|
+
(key("vol_up"), _("subir volumen")),
|
|
139
|
+
(key("vol_down"), _("bajar volumen")),
|
|
140
|
+
(key("balance_left"), _("balance a la izquierda")),
|
|
141
|
+
(key("balance_right"), _("balance a la derecha")),
|
|
142
|
+
(key("balance_centre"), _("centrar el balance")),
|
|
143
|
+
(key("equalizer"), _("ecualizador de 10 bandas")),
|
|
144
|
+
),
|
|
145
|
+
),
|
|
146
|
+
Section(
|
|
147
|
+
_("Cola"),
|
|
148
|
+
(
|
|
149
|
+
("↑ / ↓", _("mover el cursor")),
|
|
150
|
+
("pgup / pgdn", _("una página")),
|
|
151
|
+
("↵", _("reproducir la pista del cursor")),
|
|
152
|
+
(key("remove"), _("quitar la pista del cursor")),
|
|
153
|
+
(key("move_up"), _("subir la pista en la cola")),
|
|
154
|
+
(key("move_down"), _("bajar la pista en la cola")),
|
|
155
|
+
(key("clear"), _("vaciar la cola")),
|
|
156
|
+
(key("shuffle"), _("aleatorio (⇄)")),
|
|
157
|
+
(key("repeat"), _("repetición ↻: off, toda la cola, una pista")),
|
|
158
|
+
),
|
|
159
|
+
),
|
|
160
|
+
Section(
|
|
161
|
+
_("Ventanas"),
|
|
162
|
+
(
|
|
163
|
+
(key("search"), _("buscar en TIDAL")),
|
|
164
|
+
(key("library"), _("tu biblioteca")),
|
|
165
|
+
(key("lyrics"), _("letra de la pista actual")),
|
|
166
|
+
(key("equalizer"), _("ecualizador")),
|
|
167
|
+
(key("config"), _("configuración")),
|
|
168
|
+
(key("help"), _("esta ayuda")),
|
|
169
|
+
(key("quit"), _("salir")),
|
|
170
|
+
),
|
|
171
|
+
),
|
|
172
|
+
Section(
|
|
173
|
+
_("Favoritos"),
|
|
174
|
+
(
|
|
175
|
+
(key("favourite"), _("añadir a favoritos de TIDAL")),
|
|
176
|
+
(key("unfavourite"), _("quitar de favoritos")),
|
|
177
|
+
),
|
|
178
|
+
),
|
|
179
|
+
Section(
|
|
180
|
+
_("Dentro de la búsqueda y la biblioteca"),
|
|
181
|
+
(
|
|
182
|
+
("↵", _("abrir el nivel, o el menú de la pista")),
|
|
183
|
+
("← / ⌫", _("volver al nivel anterior")),
|
|
184
|
+
("a", _("añadir a la cola")),
|
|
185
|
+
("A", _("añadir el nivel entero")),
|
|
186
|
+
("R", _("recargar, ignorando la caché")),
|
|
187
|
+
("f / F", _("añadir o quitar de favoritos")),
|
|
188
|
+
("esc", _("cerrar")),
|
|
189
|
+
),
|
|
190
|
+
),
|
|
191
|
+
Section(
|
|
192
|
+
_("Menú de la pista (↵ sobre una canción)"),
|
|
193
|
+
(
|
|
194
|
+
("a", _("reproducir ahora")),
|
|
195
|
+
("c", _("reproducir a continuación")),
|
|
196
|
+
("d", _("reproducir la radio de la pista")),
|
|
197
|
+
("v", _("añadir a favoritos")),
|
|
198
|
+
("↑ / ↓ / ↵", _("elegir con el cursor")),
|
|
199
|
+
("esc", _("cancelar")),
|
|
200
|
+
),
|
|
201
|
+
),
|
|
202
|
+
)
|