wrkmon 1.0.1__py3-none-any.whl → 1.2.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.
- wrkmon/__init__.py +1 -1
- wrkmon/app.py +297 -3
- wrkmon/cli.py +100 -0
- wrkmon/core/media_keys.py +377 -0
- wrkmon/core/queue.py +55 -0
- wrkmon/core/youtube.py +102 -0
- wrkmon/data/database.py +120 -0
- wrkmon/data/migrations.py +26 -0
- wrkmon/ui/screens/help.py +80 -0
- wrkmon/ui/theme.py +332 -75
- wrkmon/ui/views/search.py +168 -12
- wrkmon/ui/widgets/header.py +31 -1
- wrkmon/ui/widgets/player_bar.py +40 -7
- wrkmon/ui/widgets/thumbnail.py +230 -0
- wrkmon/utils/ascii_art.py +408 -0
- wrkmon/utils/config.py +85 -4
- wrkmon/utils/updater.py +311 -0
- {wrkmon-1.0.1.dist-info → wrkmon-1.2.0.dist-info}/METADATA +170 -166
- {wrkmon-1.0.1.dist-info → wrkmon-1.2.0.dist-info}/RECORD +23 -18
- {wrkmon-1.0.1.dist-info → wrkmon-1.2.0.dist-info}/entry_points.txt +0 -0
- {wrkmon-1.0.1.dist-info → wrkmon-1.2.0.dist-info}/top_level.txt +0 -0
- {wrkmon-1.0.1.dist-info → wrkmon-1.2.0.dist-info}/WHEEL +0 -0
- {wrkmon-1.0.1.dist-info → wrkmon-1.2.0.dist-info}/licenses/LICENSE +0 -0
wrkmon/utils/updater.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""Version checker and updater for wrkmon."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import urllib.request
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
from packaging import version
|
|
14
|
+
|
|
15
|
+
from wrkmon import __version__
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("wrkmon.updater")
|
|
18
|
+
|
|
19
|
+
PYPI_URL = "https://pypi.org/pypi/wrkmon/json"
|
|
20
|
+
CHECK_INTERVAL_HOURS = 24
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class UpdateInfo:
|
|
25
|
+
"""Information about an available update."""
|
|
26
|
+
|
|
27
|
+
current_version: str
|
|
28
|
+
latest_version: str
|
|
29
|
+
is_update_available: bool
|
|
30
|
+
release_url: str = "https://pypi.org/project/wrkmon/"
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def update_command(self) -> str:
|
|
34
|
+
"""Get the command to update wrkmon."""
|
|
35
|
+
return "pip install --upgrade wrkmon"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_current_version() -> str:
|
|
39
|
+
"""Get the current installed version."""
|
|
40
|
+
return __version__
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def check_pypi_version() -> Optional[str]:
|
|
44
|
+
"""
|
|
45
|
+
Check PyPI for the latest version of wrkmon.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The latest version string, or None if check failed.
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
req = urllib.request.Request(
|
|
52
|
+
PYPI_URL,
|
|
53
|
+
headers={"Accept": "application/json", "User-Agent": "wrkmon-updater"}
|
|
54
|
+
)
|
|
55
|
+
with urllib.request.urlopen(req, timeout=5) as response:
|
|
56
|
+
data = json.loads(response.read().decode())
|
|
57
|
+
return data.get("info", {}).get("version")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.debug(f"Failed to check PyPI version: {e}")
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def check_pypi_version_async() -> Optional[str]:
|
|
64
|
+
"""Async wrapper for checking PyPI version."""
|
|
65
|
+
return await asyncio.to_thread(check_pypi_version)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def compare_versions(current: str, latest: str) -> bool:
|
|
69
|
+
"""
|
|
70
|
+
Check if an update is available.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
True if latest > current (update available).
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
return version.parse(latest) > version.parse(current)
|
|
77
|
+
except Exception:
|
|
78
|
+
# Fallback to string comparison
|
|
79
|
+
return latest != current and latest > current
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def check_for_updates() -> Optional[UpdateInfo]:
|
|
83
|
+
"""
|
|
84
|
+
Check if a newer version is available on PyPI.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
UpdateInfo if check succeeded, None if check failed.
|
|
88
|
+
"""
|
|
89
|
+
current = get_current_version()
|
|
90
|
+
latest = check_pypi_version()
|
|
91
|
+
|
|
92
|
+
if latest is None:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
return UpdateInfo(
|
|
96
|
+
current_version=current,
|
|
97
|
+
latest_version=latest,
|
|
98
|
+
is_update_available=compare_versions(current, latest),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def check_for_updates_async() -> Optional[UpdateInfo]:
|
|
103
|
+
"""Async version of check_for_updates."""
|
|
104
|
+
return await asyncio.to_thread(check_for_updates)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def perform_update() -> tuple[bool, str]:
|
|
108
|
+
"""
|
|
109
|
+
Attempt to update wrkmon using pip.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
tuple: (success, message)
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
result = subprocess.run(
|
|
116
|
+
[sys.executable, "-m", "pip", "install", "--upgrade", "wrkmon"],
|
|
117
|
+
capture_output=True,
|
|
118
|
+
text=True,
|
|
119
|
+
timeout=120,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if result.returncode == 0:
|
|
123
|
+
return True, "Update successful! Please restart wrkmon."
|
|
124
|
+
else:
|
|
125
|
+
return False, f"Update failed: {result.stderr}"
|
|
126
|
+
except subprocess.TimeoutExpired:
|
|
127
|
+
return False, "Update timed out. Try manually: pip install --upgrade wrkmon"
|
|
128
|
+
except Exception as e:
|
|
129
|
+
return False, f"Update error: {e}"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def perform_update_async() -> tuple[bool, str]:
|
|
133
|
+
"""Async version of perform_update."""
|
|
134
|
+
return await asyncio.to_thread(perform_update)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ============================================================
|
|
138
|
+
# Dependency checkers for better compatibility
|
|
139
|
+
# ============================================================
|
|
140
|
+
|
|
141
|
+
def is_deno_installed() -> bool:
|
|
142
|
+
"""Check if deno is installed."""
|
|
143
|
+
return shutil.which("deno") is not None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def is_nodejs_installed() -> bool:
|
|
147
|
+
"""Check if Node.js is installed."""
|
|
148
|
+
return shutil.which("node") is not None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_js_runtime() -> Optional[str]:
|
|
152
|
+
"""Get the available JavaScript runtime for yt-dlp."""
|
|
153
|
+
if is_deno_installed():
|
|
154
|
+
return "deno"
|
|
155
|
+
if is_nodejs_installed():
|
|
156
|
+
return "nodejs"
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def get_deno_install_command() -> str:
|
|
161
|
+
"""Get the command to install deno for the current platform."""
|
|
162
|
+
if sys.platform == "win32":
|
|
163
|
+
return "irm https://deno.land/install.ps1 | iex"
|
|
164
|
+
else:
|
|
165
|
+
return "curl -fsSL https://deno.land/install.sh | sh"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def install_deno() -> tuple[bool, str]:
|
|
169
|
+
"""
|
|
170
|
+
Attempt to install deno.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
tuple: (success, message)
|
|
174
|
+
"""
|
|
175
|
+
if is_deno_installed():
|
|
176
|
+
return True, "deno is already installed"
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
if sys.platform == "win32":
|
|
180
|
+
# Try winget first
|
|
181
|
+
try:
|
|
182
|
+
result = subprocess.run(
|
|
183
|
+
["winget", "install", "--id", "DenoLand.Deno", "-e", "--silent"],
|
|
184
|
+
capture_output=True,
|
|
185
|
+
timeout=300,
|
|
186
|
+
)
|
|
187
|
+
if result.returncode == 0:
|
|
188
|
+
return True, "deno installed via winget"
|
|
189
|
+
except Exception:
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
# Try scoop
|
|
193
|
+
try:
|
|
194
|
+
result = subprocess.run(
|
|
195
|
+
["scoop", "install", "deno"],
|
|
196
|
+
capture_output=True,
|
|
197
|
+
timeout=300,
|
|
198
|
+
)
|
|
199
|
+
if result.returncode == 0:
|
|
200
|
+
return True, "deno installed via scoop"
|
|
201
|
+
except Exception:
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
# Try chocolatey
|
|
205
|
+
try:
|
|
206
|
+
result = subprocess.run(
|
|
207
|
+
["choco", "install", "deno", "-y"],
|
|
208
|
+
capture_output=True,
|
|
209
|
+
timeout=300,
|
|
210
|
+
)
|
|
211
|
+
if result.returncode == 0:
|
|
212
|
+
return True, "deno installed via chocolatey"
|
|
213
|
+
except Exception:
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
return False, f"Please install deno manually:\n{get_deno_install_command()}"
|
|
217
|
+
|
|
218
|
+
else:
|
|
219
|
+
# Unix - try package managers first
|
|
220
|
+
if sys.platform == "darwin":
|
|
221
|
+
# macOS - try brew
|
|
222
|
+
try:
|
|
223
|
+
result = subprocess.run(
|
|
224
|
+
["brew", "install", "deno"],
|
|
225
|
+
capture_output=True,
|
|
226
|
+
timeout=300,
|
|
227
|
+
)
|
|
228
|
+
if result.returncode == 0:
|
|
229
|
+
return True, "deno installed via homebrew"
|
|
230
|
+
except Exception:
|
|
231
|
+
pass
|
|
232
|
+
else:
|
|
233
|
+
# Linux - try snap
|
|
234
|
+
try:
|
|
235
|
+
result = subprocess.run(
|
|
236
|
+
["snap", "install", "deno"],
|
|
237
|
+
capture_output=True,
|
|
238
|
+
timeout=300,
|
|
239
|
+
)
|
|
240
|
+
if result.returncode == 0:
|
|
241
|
+
return True, "deno installed via snap"
|
|
242
|
+
except Exception:
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
# Try the official install script
|
|
246
|
+
try:
|
|
247
|
+
result = subprocess.run(
|
|
248
|
+
["sh", "-c", "curl -fsSL https://deno.land/install.sh | sh"],
|
|
249
|
+
capture_output=True,
|
|
250
|
+
timeout=300,
|
|
251
|
+
)
|
|
252
|
+
if result.returncode == 0:
|
|
253
|
+
return True, "deno installed via official script"
|
|
254
|
+
except Exception:
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
return False, f"Please install deno manually:\n{get_deno_install_command()}"
|
|
258
|
+
|
|
259
|
+
except Exception as e:
|
|
260
|
+
return False, f"Installation failed: {e}"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
async def install_deno_async() -> tuple[bool, str]:
|
|
264
|
+
"""Async version of install_deno."""
|
|
265
|
+
return await asyncio.to_thread(install_deno)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def check_dependencies() -> dict:
|
|
269
|
+
"""
|
|
270
|
+
Check all optional dependencies for optimal functionality.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
dict with dependency status.
|
|
274
|
+
"""
|
|
275
|
+
from wrkmon.utils.mpv_installer import is_mpv_installed
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
"mpv": {
|
|
279
|
+
"installed": is_mpv_installed(),
|
|
280
|
+
"required": True,
|
|
281
|
+
"description": "Media player for audio playback",
|
|
282
|
+
},
|
|
283
|
+
"deno": {
|
|
284
|
+
"installed": is_deno_installed(),
|
|
285
|
+
"required": False,
|
|
286
|
+
"description": "JavaScript runtime for better YouTube compatibility",
|
|
287
|
+
},
|
|
288
|
+
"nodejs": {
|
|
289
|
+
"installed": is_nodejs_installed(),
|
|
290
|
+
"required": False,
|
|
291
|
+
"description": "Alternative JavaScript runtime",
|
|
292
|
+
},
|
|
293
|
+
"js_runtime": {
|
|
294
|
+
"installed": get_js_runtime() is not None,
|
|
295
|
+
"required": False,
|
|
296
|
+
"description": "JavaScript runtime (deno or nodejs) for full YouTube support",
|
|
297
|
+
},
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def get_missing_dependencies() -> list[str]:
|
|
302
|
+
"""Get list of missing recommended dependencies."""
|
|
303
|
+
deps = check_dependencies()
|
|
304
|
+
missing = []
|
|
305
|
+
|
|
306
|
+
if not deps["mpv"]["installed"]:
|
|
307
|
+
missing.append("mpv")
|
|
308
|
+
if not deps["js_runtime"]["installed"]:
|
|
309
|
+
missing.append("deno (recommended for YouTube)")
|
|
310
|
+
|
|
311
|
+
return missing
|
|
@@ -1,166 +1,170 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: wrkmon
|
|
3
|
-
Version: 1.0
|
|
4
|
-
Summary: Stealth TUI YouTube audio player - stream music while looking productive
|
|
5
|
-
Author-email: Umar Khan Yousafzai <umerfarooqkhan325@gmail.com>
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube
|
|
8
|
-
Project-URL: Documentation, https://github.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube#readme
|
|
9
|
-
Project-URL: Repository, https://github.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube
|
|
10
|
-
Project-URL: Issues, https://github.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube/issues
|
|
11
|
-
Keywords: youtube,audio,player,tui,music,stealth,productivity,terminal
|
|
12
|
-
Classifier: Development Status :: 4 - Beta
|
|
13
|
-
Classifier: Environment :: Console
|
|
14
|
-
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
-
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
-
Classifier: Operating System :: MacOS
|
|
20
|
-
Classifier: Programming Language :: Python :: 3
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
-
Classifier: Topic :: Multimedia :: Sound/Audio :: Players
|
|
25
|
-
Requires-Python: >=3.10
|
|
26
|
-
Description-Content-Type: text/markdown
|
|
27
|
-
License-File: LICENSE
|
|
28
|
-
Requires-Dist: textual>=0.50.0
|
|
29
|
-
Requires-Dist: typer>=0.9.0
|
|
30
|
-
Requires-Dist: yt-dlp>=
|
|
31
|
-
Requires-Dist: rich>=13.0.0
|
|
32
|
-
Requires-Dist:
|
|
33
|
-
|
|
34
|
-
Requires-Dist:
|
|
35
|
-
Requires-Dist:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
|
112
|
-
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
|
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
│
|
|
133
|
-
|
|
134
|
-
│
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
│
|
|
138
|
-
│
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
##
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wrkmon
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Stealth TUI YouTube audio player - stream music while looking productive
|
|
5
|
+
Author-email: Umar Khan Yousafzai <umerfarooqkhan325@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube
|
|
8
|
+
Project-URL: Documentation, https://github.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube
|
|
10
|
+
Project-URL: Issues, https://github.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube/issues
|
|
11
|
+
Keywords: youtube,audio,player,tui,music,stealth,productivity,terminal
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Operating System :: MacOS
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Players
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: textual>=0.50.0
|
|
29
|
+
Requires-Dist: typer>=0.9.0
|
|
30
|
+
Requires-Dist: yt-dlp>=2025.1.0
|
|
31
|
+
Requires-Dist: rich>=13.0.0
|
|
32
|
+
Requires-Dist: packaging>=21.0
|
|
33
|
+
Requires-Dist: Pillow>=9.0.0
|
|
34
|
+
Requires-Dist: pywin32>=306; sys_platform == "win32"
|
|
35
|
+
Requires-Dist: dbus-next>=0.2.3; sys_platform == "linux"
|
|
36
|
+
Requires-Dist: pynput>=1.7.6; sys_platform == "win32" or sys_platform == "darwin"
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
39
|
+
Requires-Dist: ruff>=0.3.0; extra == "dev"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# wrkmon
|
|
43
|
+
|
|
44
|
+
**Terminal-based YouTube Music Player** - Listen to music right from your terminal!
|
|
45
|
+
|
|
46
|
+
A beautiful TUI (Terminal User Interface) for streaming YouTube audio. No browser needed, just your terminal.
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+

|
|
50
|
+

|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- Search and stream YouTube audio
|
|
56
|
+
- Beautiful terminal interface
|
|
57
|
+
- Queue management with shuffle/repeat
|
|
58
|
+
- Play history and playlists
|
|
59
|
+
- Keyboard-driven controls
|
|
60
|
+
- Cross-platform (Windows, macOS, Linux)
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
### pip (Recommended)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install wrkmon
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
> **Note:** You also need mpv installed:
|
|
71
|
+
> - Windows: `winget install mpv`
|
|
72
|
+
> - macOS: `brew install mpv`
|
|
73
|
+
> - Linux: `sudo apt install mpv`
|
|
74
|
+
|
|
75
|
+
### Quick Install Scripts
|
|
76
|
+
|
|
77
|
+
**Windows (PowerShell):**
|
|
78
|
+
```powershell
|
|
79
|
+
irm https://raw.githubusercontent.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube/main/install.ps1 | iex
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**macOS / Linux:**
|
|
83
|
+
```bash
|
|
84
|
+
curl -sSL https://raw.githubusercontent.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube/main/install.sh | bash
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Package Managers
|
|
88
|
+
|
|
89
|
+
```powershell
|
|
90
|
+
# Windows (Chocolatey)
|
|
91
|
+
choco install wrkmon
|
|
92
|
+
|
|
93
|
+
# macOS (Homebrew) - coming soon
|
|
94
|
+
brew install wrkmon
|
|
95
|
+
|
|
96
|
+
# Linux (Snap) - coming soon
|
|
97
|
+
sudo snap install wrkmon
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Usage
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
wrkmon # Launch the TUI
|
|
104
|
+
wrkmon search "q" # Quick search
|
|
105
|
+
wrkmon play <id> # Play a video
|
|
106
|
+
wrkmon history # View history
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Keyboard Controls
|
|
110
|
+
|
|
111
|
+
| Key | Action |
|
|
112
|
+
|-----|--------|
|
|
113
|
+
| `F1` | Search view |
|
|
114
|
+
| `F2` | Queue view |
|
|
115
|
+
| `F3` | History view |
|
|
116
|
+
| `F4` | Playlists view |
|
|
117
|
+
| `F5` | Play / Pause |
|
|
118
|
+
| `F6` | Volume down |
|
|
119
|
+
| `F7` | Volume up |
|
|
120
|
+
| `F8` | Next track |
|
|
121
|
+
| `F9` | Stop |
|
|
122
|
+
| `F10` | Add to queue |
|
|
123
|
+
| `/` | Focus search |
|
|
124
|
+
| `Enter` | Play selected |
|
|
125
|
+
| `a` | Add to queue |
|
|
126
|
+
| `Ctrl+C` | Quit |
|
|
127
|
+
|
|
128
|
+
## Screenshots
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
┌─────────────────────────────────────────────────────────┐
|
|
132
|
+
│ wrkmon [Search] │
|
|
133
|
+
├─────────────────────────────────────────────────────────┤
|
|
134
|
+
│ Search: lofi beats │
|
|
135
|
+
├─────────────────────────────────────────────────────────┤
|
|
136
|
+
│ # Title Channel Duration│
|
|
137
|
+
│ 1 Lofi Hip Hop Radio ChilledCow 3:24:15│
|
|
138
|
+
│ 2 Jazz Lofi Beats Lofi Girl 2:45:00│
|
|
139
|
+
│ 3 Study Music Playlist Study 1:30:22│
|
|
140
|
+
├─────────────────────────────────────────────────────────┤
|
|
141
|
+
│ ▶ Now Playing: Lofi Beats advancement █████░░░░░ 1:23:45 │
|
|
142
|
+
│ F1 Search F2 Queue F5 Play/Pause F9 Stop │
|
|
143
|
+
└─────────────────────────────────────────────────────────┘
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Requirements
|
|
147
|
+
|
|
148
|
+
- Python 3.10+
|
|
149
|
+
- mpv media player
|
|
150
|
+
|
|
151
|
+
## Development
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
git clone https://github.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube.git
|
|
155
|
+
cd Wrkmon-TUI-Youtube
|
|
156
|
+
pip install -e ".[dev]"
|
|
157
|
+
pytest
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
163
|
+
|
|
164
|
+
## Author
|
|
165
|
+
|
|
166
|
+
**Umar Khan Yousafzai**
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
*Enjoy your music!*
|