spolyrics 1.2.1__tar.gz → 1.2.2__tar.gz
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.
- {spolyrics-1.2.1 → spolyrics-1.2.2}/LICENSE +21 -21
- {spolyrics-1.2.1/spolyrics.egg-info → spolyrics-1.2.2}/PKG-INFO +6 -2
- {spolyrics-1.2.1 → spolyrics-1.2.2}/README.md +130 -126
- {spolyrics-1.2.1 → spolyrics-1.2.2}/main.py +120 -23
- {spolyrics-1.2.1 → spolyrics-1.2.2}/setup.py +1 -1
- {spolyrics-1.2.1 → spolyrics-1.2.2/spolyrics.egg-info}/PKG-INFO +6 -2
- {spolyrics-1.2.1 → spolyrics-1.2.2}/assets.py +0 -0
- {spolyrics-1.2.1 → spolyrics-1.2.2}/setup.cfg +0 -0
- {spolyrics-1.2.1 → spolyrics-1.2.2}/spolyrics.egg-info/SOURCES.txt +0 -0
- {spolyrics-1.2.1 → spolyrics-1.2.2}/spolyrics.egg-info/dependency_links.txt +0 -0
- {spolyrics-1.2.1 → spolyrics-1.2.2}/spolyrics.egg-info/entry_points.txt +0 -0
- {spolyrics-1.2.1 → spolyrics-1.2.2}/spolyrics.egg-info/requires.txt +0 -0
- {spolyrics-1.2.1 → spolyrics-1.2.2}/spolyrics.egg-info/top_level.txt +0 -0
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 paturrr
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 paturrr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spolyrics
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: A minimalist, borderless, zero-delay synced lyrics miniplayer for Spotify on Windows.
|
|
5
5
|
Author: SpoLyrics Team
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -25,7 +25,8 @@ Dynamic: summary
|
|
|
25
25
|
<h1 align="center">SpoLyrics</h1>
|
|
26
26
|
|
|
27
27
|
<p align="center">
|
|
28
|
-
<em>A minimalist, transparent, and zero-delay Spotify lyrics miniplayer.</em>
|
|
28
|
+
<em>A minimalist, transparent, and zero-delay Spotify lyrics miniplayer.</em><br>
|
|
29
|
+
<strong>🌐 <a href="https://paturrr.github.io/SpoLyrics/">Visit the Official Website</a></strong>
|
|
29
30
|
</p>
|
|
30
31
|
|
|
31
32
|
<p align="center">
|
|
@@ -75,6 +76,9 @@ I built this project to solve a specific complaint from my friends: they wanted
|
|
|
75
76
|
|
|
76
77
|
- **Advanced Customization (Settings)**
|
|
77
78
|
Change lyrics color using a modern real-time custom HSV color picker, adjust window transparency (opacity), and enable "Auto-Start with Windows". All configurations are permanently saved.
|
|
79
|
+
|
|
80
|
+
- **Auto Update Checker**
|
|
81
|
+
Automatically checks for the newest version on PyPI when you launch the app, offering a seamless one-click update and restart experience.
|
|
78
82
|
|
|
79
83
|
## Invisible Controls
|
|
80
84
|
|
|
@@ -1,126 +1,130 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/paturrr/SpoLyrics/main/icon.ico" width="150" alt="SpoLyrics Logo">
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
|
-
<h1 align="center">SpoLyrics</h1>
|
|
6
|
-
|
|
7
|
-
<p align="center">
|
|
8
|
-
<em>A minimalist, transparent, and zero-delay Spotify lyrics miniplayer.</em>
|
|
9
|
-
</
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<img src="https://img.shields.io/
|
|
14
|
-
<img src="https://img.shields.io/badge/
|
|
15
|
-
<img src="https://img.shields.io/badge
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
```bash
|
|
83
|
-
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/paturrr/SpoLyrics/main/icon.ico" width="150" alt="SpoLyrics Logo">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">SpoLyrics</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<em>A minimalist, transparent, and zero-delay Spotify lyrics miniplayer.</em><br>
|
|
9
|
+
<strong>🌐 <a href="https://paturrr.github.io/SpoLyrics/">Visit the Official Website</a></strong>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<img src="https://img.shields.io/github/stars/paturrr/SpoLyrics.svg?style=flat-square&color=1DB954&label=stars&logo=github&logoColor=white" alt="Stars">
|
|
14
|
+
<img src="https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-1DB954?style=flat-square&logo=python&logoColor=white" alt="Python Versions">
|
|
15
|
+
<img src="https://img.shields.io/badge/OS-Windows%2010%2F11-1DB954?style=flat-square&logo=windows&logoColor=white" alt="Windows Only">
|
|
16
|
+
<img src="https://img.shields.io/badge/%F0%9F%93%9C_License-MIT-1DB954?style=flat-square" alt="MIT license">
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<strong>0-Delay Sync · Native Windows API · Glassmorphism UI · Invisible Controls</strong><br>
|
|
21
|
+
<sub>Built with Python & Windows System Media Transport Controls for 100% precision.</sub>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<p align="center">
|
|
25
|
+
<img src="https://raw.githubusercontent.com/paturrr/SpoLyrics/main/assets/1.png" alt="SpoLyrics Transparent UI & Lyrics Sync">
|
|
26
|
+
<br><br>
|
|
27
|
+
<img src="https://raw.githubusercontent.com/paturrr/SpoLyrics/main/assets/2.png" alt="SpoLyrics Modern Settings & Shortcuts">
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Background & Feedback
|
|
33
|
+
|
|
34
|
+
I built this project to solve a specific complaint from my friends: they wanted to listen to songs on Spotify or YouTube Music while working, but the official miniplayers didn't show any lyrics.
|
|
35
|
+
|
|
36
|
+
**Note:** This application is still in active development! If you encounter any bugs, have a feature request, or just want to suggest an improvement, please don't hesitate to open an issue or let me know.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Key Features
|
|
41
|
+
|
|
42
|
+
- **Zero-Delay Sync**
|
|
43
|
+
100% precise time synchronization. Pulls real-time duration data directly from the Windows OS core.
|
|
44
|
+
|
|
45
|
+
- **Smart Duration Match**
|
|
46
|
+
Automatically matches lyrics based on the duration of the currently playing song version (solving the Radio Edit vs. Album Version mismatch).
|
|
47
|
+
|
|
48
|
+
- **Glassmorphism UI**
|
|
49
|
+
Elegant, transparent, and borderless design.
|
|
50
|
+
|
|
51
|
+
- **Freely Draggable**
|
|
52
|
+
Left-click and hold anywhere to drag the lyrics window wherever you like.
|
|
53
|
+
|
|
54
|
+
- **System Tray Integration**
|
|
55
|
+
Runs quietly in the background. Right-click the system tray icon (SpoLyrics logo) to access settings, hide the lyrics, or view shortcuts.
|
|
56
|
+
|
|
57
|
+
- **Advanced Customization (Settings)**
|
|
58
|
+
Change lyrics color using a modern real-time custom HSV color picker, adjust window transparency (opacity), and enable "Auto-Start with Windows". All configurations are permanently saved.
|
|
59
|
+
|
|
60
|
+
- **Auto Update Checker**
|
|
61
|
+
Automatically checks for the newest version on PyPI when you launch the app, offering a seamless one-click update and restart experience.
|
|
62
|
+
|
|
63
|
+
## Invisible Controls
|
|
64
|
+
|
|
65
|
+
This application uses zero conventional buttons to keep the UI perfectly clean. Use these mouse gestures:
|
|
66
|
+
|
|
67
|
+
- `Right Click`: Play / Pause
|
|
68
|
+
- `Mouse Scroll (Up/Down)`: Next / Previous track
|
|
69
|
+
- `Middle Click (Scroll Wheel or 3-Finger Tap on Touchpad)`: Toggle Pin / Lock Window
|
|
70
|
+
- *When locked, the resize icon changes to a padlock (🔒) and the app ignores all drags/clicks to prevent accidental touches. Middle click again to unlock.*
|
|
71
|
+
- `Ctrl + Left Click`: Toggle song title visibility
|
|
72
|
+
- `Ctrl + Mouse Scroll`: Increase / Decrease font size
|
|
73
|
+
- `Drag Bottom Right Corner (⇲)`: Resize the window
|
|
74
|
+
- `Double Left Click`: Close the application
|
|
75
|
+
|
|
76
|
+
## Installation
|
|
77
|
+
|
|
78
|
+
Run this single command in your Windows Terminal or Command Prompt (Requires **Python 3.8 to 3.12**):
|
|
79
|
+
|
|
80
|
+
> **Note on Python Version:** Do NOT use Python 3.13. The required `winsdk` package does not yet have pre-compiled binaries for Python 3.13 and will fail to install. Please stick to Python 3.12 or older.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install spolyrics
|
|
84
|
+
```
|
|
85
|
+
*(If you get a `pip is not recognized` error, use this alternative command instead:)*
|
|
86
|
+
```bash
|
|
87
|
+
python -m pip install spolyrics
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> **IMPORTANT (OS Support):**
|
|
91
|
+
> This app is **Exclusive to Windows**. Because it uses `winsdk` (Windows System Media Transport Controls) to achieve 0-delay performance without third-party servers, it **CANNOT** be installed on macOS or Linux.
|
|
92
|
+
|
|
93
|
+
### Alternative Installation (Manual)
|
|
94
|
+
|
|
95
|
+
If you prefer to download the source code manually (e.g., as a `.zip` file) instead of using the direct `git` command above, you must install the required dependencies (the "shopping list") yourself.
|
|
96
|
+
Open your terminal inside the `SpoLyrics` folder and run:
|
|
97
|
+
```bash
|
|
98
|
+
pip install -r requirements.txt
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Usage
|
|
102
|
+
|
|
103
|
+
Once installed, you can launch the app from any directory by typing:
|
|
104
|
+
```bash
|
|
105
|
+
spolyrics
|
|
106
|
+
```
|
|
107
|
+
Or, if you want it to run smoothly without spawning a black background terminal:
|
|
108
|
+
```bash
|
|
109
|
+
pythonw -m main
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
*(Make sure Spotify or your Windows media player is running and playing a song!)*
|
|
113
|
+
|
|
114
|
+
## Updating
|
|
115
|
+
|
|
116
|
+
To update the application to the latest version, run:
|
|
117
|
+
```bash
|
|
118
|
+
pip install --upgrade spolyrics
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Uninstallation
|
|
122
|
+
|
|
123
|
+
If you wish to remove this application from your computer, simply run:
|
|
124
|
+
```bash
|
|
125
|
+
pip uninstall spolyrics
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
This project is licensed under the [MIT License](LICENSE) - see the LICENSE file for details.
|
|
@@ -12,8 +12,10 @@ import logging
|
|
|
12
12
|
import sqlite3
|
|
13
13
|
from datetime import datetime, timezone
|
|
14
14
|
from assets import ICON_B64
|
|
15
|
+
from tkinter import messagebox
|
|
15
16
|
from winsdk.windows.media.control import GlobalSystemMediaTransportControlsSessionManager as MediaManager
|
|
16
17
|
|
|
18
|
+
CURRENT_VERSION = "1.2.2"
|
|
17
19
|
CONFIG_PATH = os.path.join(os.environ.get("APPDATA", ""), "SpoLyrics", "config.json")
|
|
18
20
|
APP_DIR = os.path.join(os.environ.get("APPDATA", ""), "SpoLyrics")
|
|
19
21
|
os.makedirs(APP_DIR, exist_ok=True)
|
|
@@ -85,19 +87,25 @@ def save_config(config):
|
|
|
85
87
|
with open(CONFIG_PATH, 'w') as f:
|
|
86
88
|
json.dump(config, f)
|
|
87
89
|
|
|
88
|
-
def
|
|
90
|
+
def get_exe_path():
|
|
91
|
+
import sys
|
|
92
|
+
if getattr(sys, 'frozen', False):
|
|
93
|
+
return sys.executable
|
|
94
|
+
script_exe = os.path.join(sys.prefix, 'Scripts', 'spolyrics.exe')
|
|
95
|
+
if os.path.exists(script_exe):
|
|
96
|
+
return script_exe
|
|
97
|
+
return sys.executable
|
|
98
|
+
|
|
99
|
+
def set_auto_start(enable, force_update=False):
|
|
89
100
|
try:
|
|
90
101
|
if enable:
|
|
91
102
|
app_dir = os.path.join(os.environ.get("APPDATA", ""), "SpoLyrics")
|
|
92
103
|
icon_path = os.path.join(app_dir, 'icon.ico')
|
|
93
|
-
|
|
94
|
-
if getattr(sys, 'frozen', False):
|
|
95
|
-
exe_path = sys.executable
|
|
96
|
-
else:
|
|
97
|
-
exe_path = shutil.which('spolyrics') or sys.executable
|
|
104
|
+
exe_path = get_exe_path()
|
|
98
105
|
if exe_path and os.path.exists(icon_path):
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
if force_update or not os.path.exists(STARTUP_SHORTCUT):
|
|
107
|
+
ps_script = f"$s=(New-Object -COM WScript.Shell).CreateShortcut('{STARTUP_SHORTCUT}');$s.TargetPath='{exe_path}';$s.IconLocation='{icon_path}';$s.Save()"
|
|
108
|
+
subprocess.run(["powershell", "-Command", ps_script], creationflags=0x08000000)
|
|
101
109
|
else:
|
|
102
110
|
if os.path.exists(STARTUP_SHORTCUT):
|
|
103
111
|
os.remove(STARTUP_SHORTCUT)
|
|
@@ -138,6 +146,9 @@ class MiniLyrics:
|
|
|
138
146
|
|
|
139
147
|
self.lbl_next = tk.Label(self.root, text="", fg='#b3b3b3', bg='#191414', font=('Arial', self.font_nxt), wraplength=330, justify="center")
|
|
140
148
|
self.lbl_next.pack(expand=True, fill='both', padx=10, pady=(0, 15))
|
|
149
|
+
|
|
150
|
+
self.update_available = False
|
|
151
|
+
self.root.after(2000, self.check_updates)
|
|
141
152
|
|
|
142
153
|
self.grip = tk.Label(self.root, text="⇲", fg='#333333', bg='#191414', cursor="size_nw_se")
|
|
143
154
|
self.grip.place(relx=1.0, rely=1.0, anchor="se")
|
|
@@ -250,6 +261,16 @@ class MiniLyrics:
|
|
|
250
261
|
|
|
251
262
|
def on_scroll(self, event):
|
|
252
263
|
if self.is_pinned: return "break"
|
|
264
|
+
|
|
265
|
+
now = time.time()
|
|
266
|
+
if not hasattr(self, 'last_scroll_time'):
|
|
267
|
+
self.last_scroll_time = 0
|
|
268
|
+
|
|
269
|
+
if now - self.last_scroll_time < 0.8:
|
|
270
|
+
return "break"
|
|
271
|
+
|
|
272
|
+
self.last_scroll_time = now
|
|
273
|
+
|
|
253
274
|
if event.delta > 0: self.media_control('prev')
|
|
254
275
|
else: self.media_control('next')
|
|
255
276
|
return "break"
|
|
@@ -730,7 +751,47 @@ class MiniLyrics:
|
|
|
730
751
|
|
|
731
752
|
asyncio.run(main_loop())
|
|
732
753
|
|
|
754
|
+
def check_updates(self):
|
|
755
|
+
def _check():
|
|
756
|
+
try:
|
|
757
|
+
resp = requests.get('https://pypi.org/pypi/spolyrics/json', timeout=5).json()
|
|
758
|
+
latest = resp['info']['version']
|
|
759
|
+
if latest != CURRENT_VERSION:
|
|
760
|
+
self.root.after(0, self.show_update_prompt, latest)
|
|
761
|
+
except Exception as e:
|
|
762
|
+
logging.error("Failed to check for updates", exc_info=e)
|
|
763
|
+
threading.Thread(target=_check, daemon=True).start()
|
|
764
|
+
|
|
765
|
+
def prompt_update(self):
|
|
766
|
+
if self.update_available:
|
|
767
|
+
self.show_update_prompt(self.update_available)
|
|
768
|
+
|
|
769
|
+
def show_update_prompt(self, latest):
|
|
770
|
+
self.update_available = latest
|
|
771
|
+
if getattr(self, 'tray_icon', None):
|
|
772
|
+
self.tray_icon.update_menu()
|
|
773
|
+
|
|
774
|
+
res = messagebox.askyesno("Update Available", f"A new version of SpoLyrics (v{latest}) is available!\n\nYou are currently on v{CURRENT_VERSION}.\nWould you like to update now?")
|
|
775
|
+
if res:
|
|
776
|
+
import tempfile
|
|
777
|
+
bat_path = os.path.join(tempfile.gettempdir(), "update_spolyrics.bat")
|
|
778
|
+
exe_path = get_exe_path()
|
|
779
|
+
with open(bat_path, 'w') as f:
|
|
780
|
+
f.write('@echo off\n')
|
|
781
|
+
f.write('echo Updating SpoLyrics...\n')
|
|
782
|
+
f.write('ping 127.0.0.1 -n 3 > nul\n')
|
|
783
|
+
f.write('python -m pip install --upgrade spolyrics\n')
|
|
784
|
+
f.write(f'start "" "{exe_path}"\n')
|
|
785
|
+
f.write('del "%~f0"\n')
|
|
786
|
+
subprocess.Popen(['cmd.exe', '/c', bat_path], creationflags=16)
|
|
787
|
+
if getattr(self, 'tray_icon', None):
|
|
788
|
+
self.tray_icon.stop()
|
|
789
|
+
self.root.quit()
|
|
790
|
+
import sys
|
|
791
|
+
sys.exit(0)
|
|
792
|
+
|
|
733
793
|
def create_shortcut():
|
|
794
|
+
path_changed = False
|
|
734
795
|
try:
|
|
735
796
|
shortcut_path = os.path.join(os.environ.get("APPDATA", ""), "Microsoft", "Windows", "Start Menu", "Programs", "SpoLyrics.lnk")
|
|
736
797
|
app_dir = os.path.join(os.environ.get("APPDATA", ""), "SpoLyrics")
|
|
@@ -747,17 +808,26 @@ def create_shortcut():
|
|
|
747
808
|
if not os.path.exists(shortcut_path):
|
|
748
809
|
needs_update = True
|
|
749
810
|
|
|
811
|
+
exe_path = get_exe_path()
|
|
812
|
+
path_file = os.path.join(app_dir, 'current_exe_path.txt')
|
|
813
|
+
saved_path = ""
|
|
814
|
+
if os.path.exists(path_file):
|
|
815
|
+
with open(path_file, 'r') as f:
|
|
816
|
+
saved_path = f.read().strip()
|
|
817
|
+
|
|
818
|
+
if saved_path != exe_path:
|
|
819
|
+
needs_update = True
|
|
820
|
+
path_changed = True
|
|
821
|
+
with open(path_file, 'w') as f:
|
|
822
|
+
f.write(exe_path)
|
|
823
|
+
|
|
750
824
|
if needs_update:
|
|
751
|
-
import sys
|
|
752
|
-
if getattr(sys, 'frozen', False):
|
|
753
|
-
exe_path = sys.executable
|
|
754
|
-
else:
|
|
755
|
-
exe_path = shutil.which('spolyrics') or sys.executable
|
|
756
825
|
if exe_path:
|
|
757
826
|
ps_script = f"$s=(New-Object -COM WScript.Shell).CreateShortcut('{shortcut_path}');$s.TargetPath='{exe_path}';$s.IconLocation='{icon_path}';$s.Save()"
|
|
758
827
|
subprocess.run(["powershell", "-Command", ps_script], creationflags=0x08000000)
|
|
759
828
|
except Exception as e:
|
|
760
829
|
logging.error("Failed to create shortcut", exc_info=e)
|
|
830
|
+
return path_changed
|
|
761
831
|
|
|
762
832
|
def setup_tray(app):
|
|
763
833
|
try:
|
|
@@ -786,27 +856,54 @@ def setup_tray(app):
|
|
|
786
856
|
logging.error("Failed to open tray icon image, using fallback", exc_info=e)
|
|
787
857
|
image = Image.new('RGB', (64, 64), color = (29, 185, 84))
|
|
788
858
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
859
|
+
def get_menu():
|
|
860
|
+
items = [
|
|
861
|
+
pystray.MenuItem("Shortcuts & Info", on_info),
|
|
862
|
+
pystray.MenuItem("Settings", on_settings),
|
|
863
|
+
pystray.MenuItem("Toggle Visibility", on_toggle_lyrics),
|
|
864
|
+
pystray.MenuItem("Quit", on_quit),
|
|
865
|
+
pystray.MenuItem(f"v{CURRENT_VERSION}", lambda: None, enabled=False)
|
|
866
|
+
]
|
|
867
|
+
if getattr(app, 'update_available', False):
|
|
868
|
+
items.insert(0, pystray.MenuItem("🔥 Update Available!", lambda: app.root.after(0, app.prompt_update)))
|
|
869
|
+
return pystray.Menu(*items)
|
|
870
|
+
|
|
871
|
+
icon = pystray.Icon("SpoLyrics", image, "SpoLyrics", menu=get_menu)
|
|
872
|
+
app.tray_icon = icon
|
|
797
873
|
threading.Thread(target=icon.run, daemon=True).start()
|
|
798
874
|
except Exception as e:
|
|
799
875
|
logging.error("Tray icon failed to load", exc_info=e)
|
|
800
876
|
|
|
877
|
+
def check_single_instance():
|
|
878
|
+
mutex_name = "Global\\SpoLyrics_Mutex"
|
|
879
|
+
mutex = ctypes.windll.kernel32.CreateMutexW(None, False, mutex_name)
|
|
880
|
+
last_error = ctypes.windll.kernel32.GetLastError()
|
|
881
|
+
|
|
882
|
+
if last_error == 183:
|
|
883
|
+
from tkinter import messagebox
|
|
884
|
+
import sys
|
|
885
|
+
temp_root = tk.Tk()
|
|
886
|
+
temp_root.withdraw()
|
|
887
|
+
temp_root.attributes('-topmost', True)
|
|
888
|
+
res = messagebox.askyesno("Already Running", "SpoLyrics is already running in the background.\n\nAre you sure you want to open another instance?", parent=temp_root)
|
|
889
|
+
temp_root.destroy()
|
|
890
|
+
if not res:
|
|
891
|
+
sys.exit(0)
|
|
892
|
+
return mutex
|
|
893
|
+
|
|
801
894
|
def start_app():
|
|
895
|
+
global _app_mutex
|
|
896
|
+
_app_mutex = check_single_instance()
|
|
897
|
+
|
|
802
898
|
try:
|
|
803
899
|
ctypes.windll.shcore.SetProcessDpiAwareness(1)
|
|
804
900
|
except Exception as e:
|
|
805
901
|
logging.error("Failed to set DPI awareness", exc_info=e)
|
|
902
|
+
logging.error("Failed to set DPI awareness", exc_info=e)
|
|
806
903
|
|
|
807
|
-
create_shortcut()
|
|
904
|
+
path_changed = create_shortcut()
|
|
808
905
|
config = load_config()
|
|
809
|
-
set_auto_start(config['auto_start'])
|
|
906
|
+
set_auto_start(config['auto_start'], force_update=path_changed)
|
|
810
907
|
|
|
811
908
|
app = MiniLyrics(config)
|
|
812
909
|
setup_tray(app)
|
|
@@ -2,7 +2,7 @@ from setuptools import setup
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name='spolyrics',
|
|
5
|
-
version='1.2.
|
|
5
|
+
version='1.2.2',
|
|
6
6
|
description='A minimalist, borderless, zero-delay synced lyrics miniplayer for Spotify on Windows.',
|
|
7
7
|
long_description=open('README.md', 'r', encoding='utf-8').read(),
|
|
8
8
|
long_description_content_type='text/markdown',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spolyrics
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: A minimalist, borderless, zero-delay synced lyrics miniplayer for Spotify on Windows.
|
|
5
5
|
Author: SpoLyrics Team
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -25,7 +25,8 @@ Dynamic: summary
|
|
|
25
25
|
<h1 align="center">SpoLyrics</h1>
|
|
26
26
|
|
|
27
27
|
<p align="center">
|
|
28
|
-
<em>A minimalist, transparent, and zero-delay Spotify lyrics miniplayer.</em>
|
|
28
|
+
<em>A minimalist, transparent, and zero-delay Spotify lyrics miniplayer.</em><br>
|
|
29
|
+
<strong>🌐 <a href="https://paturrr.github.io/SpoLyrics/">Visit the Official Website</a></strong>
|
|
29
30
|
</p>
|
|
30
31
|
|
|
31
32
|
<p align="center">
|
|
@@ -75,6 +76,9 @@ I built this project to solve a specific complaint from my friends: they wanted
|
|
|
75
76
|
|
|
76
77
|
- **Advanced Customization (Settings)**
|
|
77
78
|
Change lyrics color using a modern real-time custom HSV color picker, adjust window transparency (opacity), and enable "Auto-Start with Windows". All configurations are permanently saved.
|
|
79
|
+
|
|
80
|
+
- **Auto Update Checker**
|
|
81
|
+
Automatically checks for the newest version on PyPI when you launch the app, offering a seamless one-click update and restart experience.
|
|
78
82
|
|
|
79
83
|
## Invisible Controls
|
|
80
84
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|