wrkmon 1.0.1__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/utils/config.py ADDED
@@ -0,0 +1,172 @@
1
+ """Configuration management for wrkmon."""
2
+
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ # Use tomllib for Python 3.11+, fall back to tomli
9
+ if sys.version_info >= (3, 11):
10
+ import tomllib
11
+ else:
12
+ try:
13
+ import tomli as tomllib
14
+ except ImportError:
15
+ tomllib = None
16
+
17
+
18
+ class Config:
19
+ """Manages application configuration."""
20
+
21
+ DEFAULT_CONFIG = {
22
+ "general": {
23
+ "volume": 80,
24
+ "shuffle": False,
25
+ "repeat": False,
26
+ },
27
+ "player": {
28
+ "mpv_path": "mpv",
29
+ "audio_only": True,
30
+ "no_video": True,
31
+ },
32
+ "cache": {
33
+ "url_ttl_hours": 6,
34
+ "max_entries": 1000,
35
+ },
36
+ "ui": {
37
+ "theme": "matrix", # matrix, minimal, hacker
38
+ "show_fake_stats": True,
39
+ },
40
+ }
41
+
42
+ def __init__(self):
43
+ self._config: dict[str, Any] = {}
44
+ self._config_dir = self._get_config_dir()
45
+ self._config_file = self._config_dir / "config.toml"
46
+ self._data_dir = self._get_data_dir()
47
+ self._ensure_dirs()
48
+ self._load()
49
+
50
+ def _get_config_dir(self) -> Path:
51
+ """Get the configuration directory path."""
52
+ if sys.platform == "win32":
53
+ base = Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
54
+ else:
55
+ base = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"))
56
+ return base / "wrkmon"
57
+
58
+ def _get_data_dir(self) -> Path:
59
+ """Get the data directory path."""
60
+ if sys.platform == "win32":
61
+ base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
62
+ else:
63
+ base = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share"))
64
+ return base / "wrkmon"
65
+
66
+ def _ensure_dirs(self) -> None:
67
+ """Ensure configuration and data directories exist."""
68
+ self._config_dir.mkdir(parents=True, exist_ok=True)
69
+ self._data_dir.mkdir(parents=True, exist_ok=True)
70
+
71
+ def _load(self) -> None:
72
+ """Load configuration from file."""
73
+ self._config = self.DEFAULT_CONFIG.copy()
74
+
75
+ if self._config_file.exists() and tomllib is not None:
76
+ try:
77
+ with open(self._config_file, "rb") as f:
78
+ user_config = tomllib.load(f)
79
+ self._merge_config(user_config)
80
+ except Exception:
81
+ pass # Use defaults on error
82
+
83
+ def _merge_config(self, user_config: dict[str, Any]) -> None:
84
+ """Merge user config into default config."""
85
+ for section, values in user_config.items():
86
+ if section in self._config and isinstance(values, dict):
87
+ self._config[section].update(values)
88
+ else:
89
+ self._config[section] = values
90
+
91
+ def save(self) -> None:
92
+ """Save current configuration to file."""
93
+ lines = []
94
+ for section, values in self._config.items():
95
+ lines.append(f"[{section}]")
96
+ for key, value in values.items():
97
+ if isinstance(value, bool):
98
+ lines.append(f"{key} = {str(value).lower()}")
99
+ elif isinstance(value, str):
100
+ lines.append(f'{key} = "{value}"')
101
+ else:
102
+ lines.append(f"{key} = {value}")
103
+ lines.append("")
104
+
105
+ self._config_file.write_text("\n".join(lines))
106
+
107
+ def get(self, section: str, key: str, default: Any = None) -> Any:
108
+ """Get a configuration value."""
109
+ return self._config.get(section, {}).get(key, default)
110
+
111
+ def set(self, section: str, key: str, value: Any) -> None:
112
+ """Set a configuration value."""
113
+ if section not in self._config:
114
+ self._config[section] = {}
115
+ self._config[section][key] = value
116
+
117
+ @property
118
+ def config_dir(self) -> Path:
119
+ """Get configuration directory path."""
120
+ return self._config_dir
121
+
122
+ @property
123
+ def data_dir(self) -> Path:
124
+ """Get data directory path."""
125
+ return self._data_dir
126
+
127
+ @property
128
+ def database_path(self) -> Path:
129
+ """Get database file path."""
130
+ return self._data_dir / "wrkmon.db"
131
+
132
+ @property
133
+ def cache_path(self) -> Path:
134
+ """Get cache file path."""
135
+ return self._data_dir / "cache.db"
136
+
137
+ @property
138
+ def volume(self) -> int:
139
+ """Get current volume setting."""
140
+ return self.get("general", "volume", 80)
141
+
142
+ @volume.setter
143
+ def volume(self, value: int) -> None:
144
+ """Set volume."""
145
+ self.set("general", "volume", max(0, min(100, value)))
146
+
147
+ @property
148
+ def mpv_path(self) -> str:
149
+ """Get mpv executable path."""
150
+ from wrkmon.utils.mpv_installer import get_mpv_path
151
+ configured = self.get("player", "mpv_path", "mpv")
152
+ if configured != "mpv":
153
+ return configured
154
+ # Auto-detect mpv location
155
+ return get_mpv_path()
156
+
157
+ @property
158
+ def url_ttl_hours(self) -> int:
159
+ """Get URL cache TTL in hours."""
160
+ return self.get("cache", "url_ttl_hours", 6)
161
+
162
+
163
+ # Global config instance
164
+ _config: Config | None = None
165
+
166
+
167
+ def get_config() -> Config:
168
+ """Get the global config instance."""
169
+ global _config
170
+ if _config is None:
171
+ _config = Config()
172
+ return _config
@@ -0,0 +1,190 @@
1
+ """Auto-installer for mpv on Windows."""
2
+
3
+ import os
4
+ import sys
5
+ import shutil
6
+ import zipfile
7
+ import tempfile
8
+ import urllib.request
9
+ from pathlib import Path
10
+
11
+
12
+ # mpv download URL for Windows (64-bit)
13
+ MPV_DOWNLOAD_URL = "https://sourceforge.net/projects/mpv-player-windows/files/64bit/mpv-x86_64-20240121-git-a39f9b6.7z/download"
14
+ MPV_ZIP_URL = "https://github.com/shinchiro/mpv-winbuild-cmake/releases/download/20240121/mpv-x86_64-20240121-git-a39f9b6.7z"
15
+
16
+ # Simpler: use a direct zip from a mirror or bundle
17
+ MPV_PORTABLE_URL = "https://sourceforge.net/projects/mpv-player-windows/files/bootstrapper.zip/download"
18
+
19
+
20
+ def get_mpv_dir() -> Path:
21
+ """Get the directory where mpv should be installed."""
22
+ if sys.platform == "win32":
23
+ # Install in LocalAppData/wrkmon/mpv
24
+ local_app_data = os.environ.get("LOCALAPPDATA", os.path.expanduser("~"))
25
+ return Path(local_app_data) / "wrkmon" / "mpv"
26
+ else:
27
+ # On Unix, mpv should be installed via package manager
28
+ return Path.home() / ".local" / "share" / "wrkmon" / "mpv"
29
+
30
+
31
+ def get_mpv_executable() -> Path:
32
+ """Get the path to the mpv executable."""
33
+ mpv_dir = get_mpv_dir()
34
+ if sys.platform == "win32":
35
+ # Check both possible locations
36
+ exe_path = mpv_dir / "mpv.exe"
37
+ if exe_path.exists():
38
+ return exe_path
39
+ # Also check in subdirectory (some extractions create this)
40
+ sub_path = mpv_dir / "mpv" / "mpv.exe"
41
+ if sub_path.exists():
42
+ return sub_path
43
+ return exe_path # Return default even if not exists
44
+ return mpv_dir / "mpv"
45
+
46
+
47
+ def is_mpv_installed() -> bool:
48
+ """Check if mpv is available (either in PATH or our local install)."""
49
+ # Check PATH first
50
+ if shutil.which("mpv"):
51
+ return True
52
+
53
+ # Check our local install
54
+ mpv_exe = get_mpv_executable()
55
+ return mpv_exe.exists()
56
+
57
+
58
+ def get_mpv_path() -> str:
59
+ """Get the path to mpv executable."""
60
+ # Check PATH first
61
+ system_mpv = shutil.which("mpv")
62
+ if system_mpv:
63
+ return system_mpv
64
+
65
+ # Check our local install
66
+ mpv_exe = get_mpv_executable()
67
+ if mpv_exe.exists():
68
+ return str(mpv_exe)
69
+
70
+ return "mpv" # Default, will fail if not installed
71
+
72
+
73
+ def download_file(url: str, dest: Path, progress_callback=None) -> bool:
74
+ """Download a file with optional progress callback."""
75
+ try:
76
+ def report_progress(block_num, block_size, total_size):
77
+ if progress_callback and total_size > 0:
78
+ progress = min(100, (block_num * block_size * 100) // total_size)
79
+ progress_callback(progress)
80
+
81
+ urllib.request.urlretrieve(url, dest, reporthook=report_progress)
82
+ return True
83
+ except Exception as e:
84
+ print(f"Download failed: {e}")
85
+ return False
86
+
87
+
88
+ def install_mpv_windows(progress_callback=None) -> bool:
89
+ """Download and install mpv on Windows."""
90
+ import subprocess
91
+
92
+ mpv_dir = get_mpv_dir()
93
+ mpv_dir.mkdir(parents=True, exist_ok=True)
94
+
95
+ # Use winget if available (cleanest option)
96
+ try:
97
+ result = subprocess.run(
98
+ ["winget", "install", "--id", "mpv.net", "-e", "--silent"],
99
+ capture_output=True,
100
+ timeout=300
101
+ )
102
+ if result.returncode == 0:
103
+ return True
104
+ except Exception:
105
+ pass
106
+
107
+ # Try chocolatey
108
+ try:
109
+ result = subprocess.run(
110
+ ["choco", "install", "mpv", "-y"],
111
+ capture_output=True,
112
+ timeout=300
113
+ )
114
+ if result.returncode == 0:
115
+ return True
116
+ except Exception:
117
+ pass
118
+
119
+ # Manual download as fallback
120
+ # Download portable mpv
121
+ with tempfile.TemporaryDirectory() as tmp_dir:
122
+ tmp_path = Path(tmp_dir)
123
+
124
+ if progress_callback:
125
+ progress_callback(0, "Downloading mpv...")
126
+
127
+ # Try to download from GitHub releases (more reliable)
128
+ zip_path = tmp_path / "mpv.zip"
129
+
130
+ # Use a known working URL for portable mpv
131
+ urls_to_try = [
132
+ "https://github.com/shinchiro/mpv-winbuild-cmake/releases/latest/download/mpv-x86_64-latest.7z",
133
+ "https://downloads.sourceforge.net/project/mpv-player-windows/64bit-v3/mpv-x86_64-v3-20240114-git-5765e7f.7z",
134
+ ]
135
+
136
+ # For simplicity, let's just tell users to install manually if auto-install fails
137
+ if progress_callback:
138
+ progress_callback(100, "Please install mpv manually: winget install mpv")
139
+
140
+ return False
141
+
142
+
143
+ def install_mpv(progress_callback=None) -> bool:
144
+ """Install mpv for the current platform."""
145
+ if sys.platform == "win32":
146
+ return install_mpv_windows(progress_callback)
147
+ else:
148
+ # On Unix, tell user to install via package manager
149
+ print("Please install mpv using your package manager:")
150
+ print(" Ubuntu/Debian: sudo apt install mpv")
151
+ print(" Fedora: sudo dnf install mpv")
152
+ print(" Arch: sudo pacman -S mpv")
153
+ print(" macOS: brew install mpv")
154
+ return False
155
+
156
+
157
+ def ensure_mpv_installed() -> tuple[bool, str]:
158
+ """
159
+ Ensure mpv is installed, attempting auto-install if needed.
160
+
161
+ Returns:
162
+ tuple: (success: bool, mpv_path_or_error: str)
163
+ """
164
+ if is_mpv_installed():
165
+ return True, get_mpv_path()
166
+
167
+ # Try to install
168
+ print("mpv not found, attempting to install...")
169
+ if install_mpv():
170
+ if is_mpv_installed():
171
+ return True, get_mpv_path()
172
+
173
+ # Installation failed
174
+ if sys.platform == "win32":
175
+ error_msg = (
176
+ "mpv not found! Please install it:\n"
177
+ " Option 1: winget install mpv\n"
178
+ " Option 2: choco install mpv\n"
179
+ " Option 3: Download from https://mpv.io/installation/"
180
+ )
181
+ else:
182
+ error_msg = (
183
+ "mpv not found! Please install it:\n"
184
+ " Ubuntu/Debian: sudo apt install mpv\n"
185
+ " Fedora: sudo dnf install mpv\n"
186
+ " Arch: sudo pacman -S mpv\n"
187
+ " macOS: brew install mpv"
188
+ )
189
+
190
+ return False, error_msg
@@ -0,0 +1,124 @@
1
+ """Stealth utilities for wrkmon - makes everything look like a dev tool."""
2
+
3
+ import os
4
+ import sys
5
+ import random
6
+ from typing import Optional
7
+
8
+
9
+ class StealthManager:
10
+ """Manages stealth features for the application."""
11
+
12
+ # Fake process names that look like legitimate dev tools
13
+ FAKE_PROCESS_NAMES = [
14
+ "node-inspector",
15
+ "webpack-dev-srv",
16
+ "vite-hmr-watch",
17
+ "eslint-daemon",
18
+ "tsc-watch",
19
+ "pytest-runner",
20
+ "cargo-watch",
21
+ "go-build-srv",
22
+ "rust-analyzer",
23
+ "prettier-fmt",
24
+ ]
25
+
26
+ # Fake CPU/Memory stats ranges for the UI
27
+ CPU_RANGE = (12, 45)
28
+ MEM_RANGE = (35, 65)
29
+
30
+ def __init__(self):
31
+ self._original_title: Optional[str] = None
32
+
33
+ def get_pipe_name(self) -> str:
34
+ """Get the IPC pipe/socket name for mpv."""
35
+ if sys.platform == "win32":
36
+ return r"\\.\pipe\wrkmon-mpv"
37
+ else:
38
+ # Unix socket in runtime dir
39
+ runtime_dir = os.environ.get("XDG_RUNTIME_DIR", "/tmp")
40
+ return f"{runtime_dir}/wrkmon-mpv.sock"
41
+
42
+ def get_fake_process_name(self, video_title: str) -> str:
43
+ """Convert a video title to a fake process name."""
44
+ # Sanitize and truncate the title
45
+ name = video_title.lower()
46
+ # Replace spaces and special chars with hyphens
47
+ name = "".join(c if c.isalnum() else "-" for c in name)
48
+ # Remove consecutive hyphens
49
+ while "--" in name:
50
+ name = name.replace("--", "-")
51
+ # Trim and limit length
52
+ name = name.strip("-")[:30]
53
+ return name or "media-process"
54
+
55
+ def get_fake_pid(self) -> int:
56
+ """Generate a fake PID that looks realistic."""
57
+ return random.randint(1000, 65535)
58
+
59
+ def get_fake_cpu(self) -> int:
60
+ """Get a fake CPU usage percentage."""
61
+ return random.randint(*self.CPU_RANGE)
62
+
63
+ def get_fake_memory(self) -> int:
64
+ """Get a fake memory usage percentage."""
65
+ return random.randint(*self.MEM_RANGE)
66
+
67
+ def set_terminal_title(self, title: str = "wrkmon") -> None:
68
+ """Set the terminal window title."""
69
+ if sys.platform == "win32":
70
+ os.system(f"title {title}")
71
+ else:
72
+ # ANSI escape sequence for setting terminal title
73
+ sys.stdout.write(f"\033]0;{title}\007")
74
+ sys.stdout.flush()
75
+
76
+ def restore_terminal_title(self) -> None:
77
+ """Restore the original terminal title."""
78
+ if self._original_title:
79
+ self.set_terminal_title(self._original_title)
80
+
81
+ def get_mpv_args(self) -> list[str]:
82
+ """Get mpv arguments for stealth operation."""
83
+ return [
84
+ "--no-video",
85
+ "--no-terminal",
86
+ "--really-quiet",
87
+ f"--input-ipc-server={self.get_pipe_name()}",
88
+ "--idle=yes",
89
+ "--force-window=no",
90
+ ]
91
+
92
+ def format_status(self, status: str) -> str:
93
+ """Format a status string to look like a system status."""
94
+ status_map = {
95
+ "playing": "RUNNING",
96
+ "paused": "SUSPENDED",
97
+ "stopped": "STOPPED",
98
+ "buffering": "LOADING",
99
+ "ready": "READY",
100
+ "error": "FAILED",
101
+ }
102
+ return status_map.get(status.lower(), status.upper())
103
+
104
+ def format_duration(self, seconds: float) -> str:
105
+ """Format duration in a clean way."""
106
+ if seconds < 0:
107
+ return "--:--"
108
+ mins, secs = divmod(int(seconds), 60)
109
+ hours, mins = divmod(mins, 60)
110
+ if hours > 0:
111
+ return f"{hours}:{mins:02d}:{secs:02d}"
112
+ return f"{mins}:{secs:02d}"
113
+
114
+
115
+ # Global stealth manager instance
116
+ _stealth: Optional[StealthManager] = None
117
+
118
+
119
+ def get_stealth() -> StealthManager:
120
+ """Get the global stealth manager instance."""
121
+ global _stealth
122
+ if _stealth is None:
123
+ _stealth = StealthManager()
124
+ return _stealth
@@ -0,0 +1,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: wrkmon
3
+ Version: 1.0.1
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>=2024.0.0
31
+ Requires-Dist: rich>=13.0.0
32
+ Requires-Dist: pywin32>=306; sys_platform == "win32"
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
35
+ Requires-Dist: ruff>=0.3.0; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # wrkmon
39
+
40
+ **Terminal-based YouTube Music Player** - Listen to music right from your terminal!
41
+
42
+ A beautiful TUI (Terminal User Interface) for streaming YouTube audio. No browser needed, just your terminal.
43
+
44
+ ![License](https://img.shields.io/badge/license-MIT-blue.svg)
45
+ ![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)
46
+ ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)
47
+ ![PyPI](https://img.shields.io/pypi/v/wrkmon.svg)
48
+
49
+ ## Features
50
+
51
+ - Search and stream YouTube audio
52
+ - Beautiful terminal interface
53
+ - Queue management with shuffle/repeat
54
+ - Play history and playlists
55
+ - Keyboard-driven controls
56
+ - Cross-platform (Windows, macOS, Linux)
57
+
58
+ ## Installation
59
+
60
+ ### pip (Recommended)
61
+
62
+ ```bash
63
+ pip install wrkmon
64
+ ```
65
+
66
+ > **Note:** You also need mpv installed:
67
+ > - Windows: `winget install mpv`
68
+ > - macOS: `brew install mpv`
69
+ > - Linux: `sudo apt install mpv`
70
+
71
+ ### Quick Install Scripts
72
+
73
+ **Windows (PowerShell):**
74
+ ```powershell
75
+ irm https://raw.githubusercontent.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube/main/install.ps1 | iex
76
+ ```
77
+
78
+ **macOS / Linux:**
79
+ ```bash
80
+ curl -sSL https://raw.githubusercontent.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube/main/install.sh | bash
81
+ ```
82
+
83
+ ### Package Managers
84
+
85
+ ```powershell
86
+ # Windows (Chocolatey)
87
+ choco install wrkmon
88
+
89
+ # macOS (Homebrew) - coming soon
90
+ brew install wrkmon
91
+
92
+ # Linux (Snap) - coming soon
93
+ sudo snap install wrkmon
94
+ ```
95
+
96
+ ## Usage
97
+
98
+ ```bash
99
+ wrkmon # Launch the TUI
100
+ wrkmon search "q" # Quick search
101
+ wrkmon play <id> # Play a video
102
+ wrkmon history # View history
103
+ ```
104
+
105
+ ## Keyboard Controls
106
+
107
+ | Key | Action |
108
+ |-----|--------|
109
+ | `F1` | Search view |
110
+ | `F2` | Queue view |
111
+ | `F3` | History view |
112
+ | `F4` | Playlists view |
113
+ | `F5` | Play / Pause |
114
+ | `F6` | Volume down |
115
+ | `F7` | Volume up |
116
+ | `F8` | Next track |
117
+ | `F9` | Stop |
118
+ | `F10` | Add to queue |
119
+ | `/` | Focus search |
120
+ | `Enter` | Play selected |
121
+ | `a` | Add to queue |
122
+ | `Ctrl+C` | Quit |
123
+
124
+ ## Screenshots
125
+
126
+ ```
127
+ ┌─────────────────────────────────────────────────────────┐
128
+ │ wrkmon [Search] │
129
+ ├─────────────────────────────────────────────────────────┤
130
+ │ Search: lofi beats │
131
+ ├─────────────────────────────────────────────────────────┤
132
+ │ # Title Channel Duration│
133
+ │ 1 Lofi Hip Hop Radio ChilledCow 3:24:15│
134
+ │ 2 Jazz Lofi Beats Lofi Girl 2:45:00│
135
+ │ 3 Study Music Playlist Study 1:30:22│
136
+ ├─────────────────────────────────────────────────────────┤
137
+ │ ▶ Now Playing: Lofi Beats advancement █████░░░░░ 1:23:45 │
138
+ │ F1 Search F2 Queue F5 Play/Pause F9 Stop │
139
+ └─────────────────────────────────────────────────────────┘
140
+ ```
141
+
142
+ ## Requirements
143
+
144
+ - Python 3.10+
145
+ - mpv media player
146
+
147
+ ## Development
148
+
149
+ ```bash
150
+ git clone https://github.com/Umar-Khan-Yousafzai/Wrkmon-TUI-Youtube.git
151
+ cd Wrkmon-TUI-Youtube
152
+ pip install -e ".[dev]"
153
+ pytest
154
+ ```
155
+
156
+ ## License
157
+
158
+ MIT License - see [LICENSE](LICENSE) for details.
159
+
160
+ ## Author
161
+
162
+ **Umar Khan Yousafzai**
163
+
164
+ ---
165
+
166
+ *Enjoy your music!*
@@ -0,0 +1,41 @@
1
+ wrkmon/__init__.py,sha256=X5ppzR6k_fMsxNUhNdnCjgV-UQvrY86IWvjaFrDGaqs,107
2
+ wrkmon/__main__.py,sha256=27UFV2ULX5B8OO5b9HjCtTu5k6hdKXnQlCcNVwlYKto,116
3
+ wrkmon/app.py,sha256=kkGTiloPttmJhnV6Yh6Bnn0YkSDWkwtq-eYKnhpMyPw,21346
4
+ wrkmon/cli.py,sha256=2K72I4PrgivPt4OP14XZjYjWNS7mvfTtcKf4YgZ2JZ4,8189
5
+ wrkmon/core/__init__.py,sha256=50AiIHwvm2hVSBc5qkca7_k8taS4IIs_J8i0irz-rBo,269
6
+ wrkmon/core/cache.py,sha256=-3ZH4GPQt1xk8huq50BuCNinTLFbZzRPXhPAEnmWZAs,6599
7
+ wrkmon/core/player.py,sha256=ZeLvffoid8BUXZA9GDHt1YtQhzkmL9jlAhRfXtOc4AQ,9709
8
+ wrkmon/core/queue.py,sha256=WgIkyi8uHRGLeX1mZoI8o10ABRrCSCLFiZRD20wCNzU,8563
9
+ wrkmon/core/youtube.py,sha256=M0IvRErqec0RaVq58z07L45igxpbkqEiavkDQHAXzac,6215
10
+ wrkmon/data/__init__.py,sha256=-geRAYau8OCtGztlSg4gNkmpNC1LEYbH5bI5y-4SYoo,194
11
+ wrkmon/data/database.py,sha256=Ky1QRq5LZewRtWul_4qWJfoXbKbO7Nw--Ll9QKe9PrU,13448
12
+ wrkmon/data/migrations.py,sha256=E2qBzEVrqp50b5WoQ1tB6hAPgLh50ZGq5YGSp06JPfE,4723
13
+ wrkmon/data/models.py,sha256=C2rmdHnFr6BqRVjkWW6oZ2m6pSXrwMlp84l_7qxwj6A,4305
14
+ wrkmon/ui/__init__.py,sha256=fy6EMFEEOA2k4rzEUsWl93hW_11bn1cYT29qVontggc,111
15
+ wrkmon/ui/components.py,sha256=NaPQbDHnBM1LBZL_r5JcXvxEaCxwoizpMxWGFAAJTTs,7117
16
+ wrkmon/ui/messages.py,sha256=0ds2nvaHQ6-2Ok8hfII6XlFkbNeAnCRFa7KldKZ4m8I,2352
17
+ wrkmon/ui/theme.py,sha256=fVEVpokqEX3BmykS0KdndtjgV6NU8ZCYr7LHo0cH0qE,5260
18
+ wrkmon/ui/screens/__init__.py,sha256=0_MX_qc8di-fRCm58b14nIV7X7iMZAJxfLIxYCwJo4I,316
19
+ wrkmon/ui/screens/history.py,sha256=teRm0HhaWgHKXsu57XQRScgYqn_MvK9qA-5IBoWffRE,4831
20
+ wrkmon/ui/screens/player.py,sha256=NPIV1oobtPdBZEHwpSTXUZeV8R1yrW02AVsoLuL3PZI,8046
21
+ wrkmon/ui/screens/playlist.py,sha256=0m9kIUpJP4QHJTTzP_pKBuZS3gUfZZlhsWVy_iHZp4U,9676
22
+ wrkmon/ui/screens/search.py,sha256=lYwTBbzrIW0amln0o9Hs-RtTt2FqHr2QasIVhxq3FKo,5626
23
+ wrkmon/ui/views/__init__.py,sha256=WNHN2SDDk3f1k9VLHdpTbhWotRw_cCHVkR4-OtbfACo,308
24
+ wrkmon/ui/views/history.py,sha256=j9keAwnOMSGoqFKlT21bpxJlhJqKNMfv7X7-eP80sb0,4895
25
+ wrkmon/ui/views/playlists.py,sha256=5yvknFiQ7saaM_t28n-7JhY28lRxN6z3BFspSwXKyKA,9411
26
+ wrkmon/ui/views/queue.py,sha256=_QmSfFrjFNcrNdfetTbojJpCwE3FPR-ed3bO9BsFlIs,6811
27
+ wrkmon/ui/views/search.py,sha256=lAD-M5x0p89cC2cWoFTff_6RTOLBb3maDaVaodY1jTw,9865
28
+ wrkmon/ui/widgets/__init__.py,sha256=MZZLVqMv5cKOXhEJ-XZ3vVbrcLkg1wpFBtiIItv6HXE,250
29
+ wrkmon/ui/widgets/header.py,sha256=vwZ1pRxROsWgXWaqdVne3_phiCfcCKeYEWg1ubrI7w8,1843
30
+ wrkmon/ui/widgets/player_bar.py,sha256=i6EnHlozAG4o7SRQMUGzaRqYk7wcH56FnQU7_4m2uDY,4824
31
+ wrkmon/ui/widgets/result_item.py,sha256=BroroFbMbT3qoH-Up1RJk3mfDwRCxNqodKgd_K1MeKM,3150
32
+ wrkmon/utils/__init__.py,sha256=C1P1hbS96YERgJTw3zGqEayxwr0AFrDhCAuT0WtPO00,162
33
+ wrkmon/utils/config.py,sha256=0v1VO8YFShFJcF5CQhHenKPVr_tm_r6JtaLJy39GRaE,5332
34
+ wrkmon/utils/mpv_installer.py,sha256=HGS8Sq5ZJMCAZk2OTkHDo2ZyqzmXtaHoxR0guEdMstk,6227
35
+ wrkmon/utils/stealth.py,sha256=EmI0-rUYhdV2fIZ2evwdPlSkqFT-Lq6fEX3ZqQH430A,3901
36
+ wrkmon-1.0.1.dist-info/licenses/LICENSE,sha256=0cLcTBgN-yLwY5jhlp8oK1wBxUCyIH7I4vst8RPxelw,1097
37
+ wrkmon-1.0.1.dist-info/METADATA,sha256=yuFgRbGigyOwJpUR_CVyt4Rdp2CxjEEJ3DzE5xGIptc,5555
38
+ wrkmon-1.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
39
+ wrkmon-1.0.1.dist-info/entry_points.txt,sha256=sI_CTRHFwhiEHh5BJZtpwiBsfQekpqXcXiT31wcdUZo,42
40
+ wrkmon-1.0.1.dist-info/top_level.txt,sha256=lQvb7Xi0gQQ8R9z1IkR39vTdL-0jxZreD5McnnPFtLs,7
41
+ wrkmon-1.0.1.dist-info/RECORD,,