explayer 1.0.0__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.
- explayer-1.0.0/LICENSE.txt +7 -0
- explayer-1.0.0/PKG-INFO +22 -0
- explayer-1.0.0/README.md +4 -0
- explayer-1.0.0/explayer/__init__.py +1 -0
- explayer-1.0.0/explayer/player.py +506 -0
- explayer-1.0.0/explayer.egg-info/PKG-INFO +22 -0
- explayer-1.0.0/explayer.egg-info/SOURCES.txt +11 -0
- explayer-1.0.0/explayer.egg-info/dependency_links.txt +1 -0
- explayer-1.0.0/explayer.egg-info/entry_points.txt +2 -0
- explayer-1.0.0/explayer.egg-info/requires.txt +1 -0
- explayer-1.0.0/explayer.egg-info/top_level.txt +1 -0
- explayer-1.0.0/pyproject.toml +25 -0
- explayer-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Aritro Halder
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
explayer-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: explayer
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A terminal-based music player with synchronized LRC lyrics
|
|
5
|
+
Author-email: Aritro Halder <studiozzzz033@gmail.com>
|
|
6
|
+
License: Copyright 2026 Aritro Halder
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE.txt
|
|
16
|
+
Requires-Dist: pygame>=2.6.0
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# eXPlayer
|
|
20
|
+
eXPlayer is a terminal based music player with lyrics support. The player includes a command window to change settings and other functions!
|
|
21
|
+
|
|
22
|
+
# Screenshot and videos
|
explayer-1.0.0/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
import msvcrt
|
|
5
|
+
import shutil
|
|
6
|
+
import os
|
|
7
|
+
import random
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
#global variables and stuff
|
|
12
|
+
|
|
13
|
+
global playing
|
|
14
|
+
playing = False
|
|
15
|
+
paused = False
|
|
16
|
+
song_name = "No Song Name found"
|
|
17
|
+
current_lyric = "♪ Lyrics ♪"
|
|
18
|
+
current_time = 0
|
|
19
|
+
total_time = 0
|
|
20
|
+
WIDTH = shutil.get_terminal_size().columns
|
|
21
|
+
last_width = WIDTH
|
|
22
|
+
songs = []
|
|
23
|
+
current_song_index = 0
|
|
24
|
+
song_length = 0
|
|
25
|
+
music_folder = "music"
|
|
26
|
+
selected_song = None
|
|
27
|
+
shuffle_mode = True
|
|
28
|
+
|
|
29
|
+
#config save and load function
|
|
30
|
+
|
|
31
|
+
def get_config_path():
|
|
32
|
+
appdata = os.getenv("LOCALAPPDATA")
|
|
33
|
+
config_dir = os.path.join(appdata, "eXPlayer")
|
|
34
|
+
os.makedirs(config_dir,exist_ok=True)
|
|
35
|
+
return os.path.join(config_dir, "config.json")
|
|
36
|
+
|
|
37
|
+
def load_config():
|
|
38
|
+
global music_folder,shuffle_mode,songs
|
|
39
|
+
|
|
40
|
+
config_path = get_config_path()
|
|
41
|
+
|
|
42
|
+
if os.path.exists(config_path):
|
|
43
|
+
with open(config_path,"r") as f:
|
|
44
|
+
config = json.load(f)
|
|
45
|
+
music_folder = config.get("music_folder","music")
|
|
46
|
+
shuffle_mode = config.get("shuffle_mode",True)
|
|
47
|
+
|
|
48
|
+
songs = get_songs()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def save_config():
|
|
52
|
+
config = {
|
|
53
|
+
"music_folder": music_folder,
|
|
54
|
+
"shuffle_mode": shuffle_mode
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
config_path = get_config_path()
|
|
58
|
+
|
|
59
|
+
print(f"\nCONFIG SAVED TO: {config_path}\n")
|
|
60
|
+
|
|
61
|
+
with open(config_path,"w") as f:
|
|
62
|
+
json.dump(config, f, indent=4)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
#the time converting function for song
|
|
66
|
+
|
|
67
|
+
def format_time(seconds):
|
|
68
|
+
minutes = int(seconds //60)
|
|
69
|
+
seconds = int(seconds % 60)
|
|
70
|
+
return f"{minutes:02d}:{seconds:02d}"
|
|
71
|
+
|
|
72
|
+
total_time = format_time(song_length)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def load_songs(song_path):
|
|
76
|
+
global song_name, song_length, total_time, lyrics_path, Lyrics
|
|
77
|
+
pygame.mixer.music.load(song_path)
|
|
78
|
+
|
|
79
|
+
song_name = os.path.splitext(os.path.basename(song_path))[0]
|
|
80
|
+
song_length = pygame.mixer.Sound(song_path).get_length()
|
|
81
|
+
|
|
82
|
+
total_time = format_time(song_length)
|
|
83
|
+
|
|
84
|
+
lyrics_path = os.path.splitext(song_path)[0] + ".lrc"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
#lyrics converting stuff
|
|
88
|
+
|
|
89
|
+
Lyrics = []
|
|
90
|
+
|
|
91
|
+
if os.path.exists(lyrics_path):
|
|
92
|
+
with open(lyrics_path, encoding="utf-8") as file:
|
|
93
|
+
for line in file:
|
|
94
|
+
line = line.strip()
|
|
95
|
+
|
|
96
|
+
if not line.startswith("[") or "]" not in line:
|
|
97
|
+
continue
|
|
98
|
+
try:
|
|
99
|
+
time_stamp, lyric = line.strip().split("]", 1)
|
|
100
|
+
time_stamp = time_stamp[1:]
|
|
101
|
+
|
|
102
|
+
minutes, seconds = time_stamp.split(":")
|
|
103
|
+
lyric_time = int(minutes) * 60 + float(seconds)
|
|
104
|
+
|
|
105
|
+
Lyrics.append((lyric_time, lyric))
|
|
106
|
+
except ValueError:
|
|
107
|
+
continue
|
|
108
|
+
else:
|
|
109
|
+
Lyrics = [(0, "♪ No lyrics found ♪")]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def select_song(song_path):
|
|
113
|
+
global selected_song
|
|
114
|
+
|
|
115
|
+
load_songs(song_path)
|
|
116
|
+
selected_song = song_path
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def select_first_song():
|
|
120
|
+
global selected_song, current_song_index, playing
|
|
121
|
+
|
|
122
|
+
songs = get_songs()
|
|
123
|
+
if not songs:
|
|
124
|
+
return
|
|
125
|
+
current_song_index = 0
|
|
126
|
+
selected_song = songs[0]
|
|
127
|
+
load_songs(selected_song)
|
|
128
|
+
pygame.mixer.music.play()
|
|
129
|
+
playing = True
|
|
130
|
+
|
|
131
|
+
print(f"\033[9;1H\033[2K", end="")
|
|
132
|
+
print(song_name.center(WIDTH), end="", flush=True)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def play_next_shuffle():
|
|
136
|
+
global selected_song,current_song_index
|
|
137
|
+
|
|
138
|
+
songs = get_songs()
|
|
139
|
+
|
|
140
|
+
if not songs:
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
current_song_index = random.randrange(len(songs))
|
|
144
|
+
selected_song = songs[current_song_index]
|
|
145
|
+
|
|
146
|
+
load_songs(selected_song)
|
|
147
|
+
pygame.mixer.music.play()
|
|
148
|
+
|
|
149
|
+
print(f"\033[9;1H\033[2K", end="")
|
|
150
|
+
print(song_name.center(WIDTH), end="", flush=True)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def play_next():
|
|
154
|
+
global selected_song, current_song_index,playing
|
|
155
|
+
|
|
156
|
+
songs = get_songs()
|
|
157
|
+
if not songs:
|
|
158
|
+
return
|
|
159
|
+
current_song_index += 1
|
|
160
|
+
|
|
161
|
+
if current_song_index >= len(songs):
|
|
162
|
+
current_song_index = 0
|
|
163
|
+
|
|
164
|
+
selected_song = songs[current_song_index]
|
|
165
|
+
load_songs(selected_song)
|
|
166
|
+
pygame.mixer.music.play()
|
|
167
|
+
time.sleep(0.1)
|
|
168
|
+
|
|
169
|
+
playing = True
|
|
170
|
+
|
|
171
|
+
print(f"\033[9;1H\033[2K", end="")
|
|
172
|
+
print(song_name.center(WIDTH), end="", flush=True)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def play_previous():
|
|
176
|
+
global selected_song, current_song_index, playing
|
|
177
|
+
|
|
178
|
+
songs = get_songs()
|
|
179
|
+
if not songs:
|
|
180
|
+
return
|
|
181
|
+
current_song_index -= 1
|
|
182
|
+
|
|
183
|
+
if current_song_index <0:
|
|
184
|
+
current_song_index = len(songs) -1
|
|
185
|
+
|
|
186
|
+
selected_song = songs[current_song_index]
|
|
187
|
+
load_songs(selected_song)
|
|
188
|
+
pygame.mixer.music.play()
|
|
189
|
+
time.sleep(0.1)
|
|
190
|
+
|
|
191
|
+
playing = True
|
|
192
|
+
|
|
193
|
+
print(f"\033[9;1H\033[2K", end="")
|
|
194
|
+
print(song_name.center(WIDTH), end="", flush=True)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def list_folder():
|
|
198
|
+
if not os.path.exists(music_folder):
|
|
199
|
+
print("Music folder not found. Please select a folder with mp3 files")
|
|
200
|
+
return
|
|
201
|
+
print("Available songs:")
|
|
202
|
+
print(f"Contents of: {music_folder}")
|
|
203
|
+
|
|
204
|
+
files = os.listdir(music_folder)
|
|
205
|
+
|
|
206
|
+
for file in files:
|
|
207
|
+
print(file)
|
|
208
|
+
print()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def get_songs():
|
|
212
|
+
if not os.path.isdir(music_folder):
|
|
213
|
+
return []
|
|
214
|
+
return [os.path.join(music_folder,file)
|
|
215
|
+
for file in os.listdir(music_folder)
|
|
216
|
+
if file.lower().endswith(".mp3")
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
#drawing the ui
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def draw_ui():
|
|
224
|
+
|
|
225
|
+
logo = r"""
|
|
226
|
+
____ _____________.__
|
|
227
|
+
____ \ \/ /\______ \ | _____ ___.__. ___________
|
|
228
|
+
_/ __ \ \ / | ___/ | \__ \< | |/ __ \_ __ \
|
|
229
|
+
\ ___/ / \ | | | |__/ __ \\___ \ ___/| | \/
|
|
230
|
+
\___ >___/\ \ |____| |____(____ / ____|\___ >__|
|
|
231
|
+
\/ \_/ \/\/ \/
|
|
232
|
+
"""
|
|
233
|
+
logo_lines = logo.splitlines()
|
|
234
|
+
|
|
235
|
+
logo_width = max(len(line) for line in logo_lines)
|
|
236
|
+
|
|
237
|
+
# Calculate the left padding needed to center the entire logo
|
|
238
|
+
padding = max(0, (WIDTH - logo_width) // 2)
|
|
239
|
+
|
|
240
|
+
for line in logo_lines:
|
|
241
|
+
print(" " * padding + line)
|
|
242
|
+
|
|
243
|
+
print(song_name.center(WIDTH))
|
|
244
|
+
print()
|
|
245
|
+
|
|
246
|
+
print(current_lyric.center(WIDTH))
|
|
247
|
+
print()
|
|
248
|
+
|
|
249
|
+
print("progress_bar".center(WIDTH))
|
|
250
|
+
print()
|
|
251
|
+
|
|
252
|
+
print(f"{current_time} / {total_time}".center(WIDTH))
|
|
253
|
+
print()
|
|
254
|
+
|
|
255
|
+
print("[ P ] Play [ O ] Pause".center(WIDTH))
|
|
256
|
+
print("[ B ] Previous [ N ] Next ".center(WIDTH))
|
|
257
|
+
print("[ R ] Resume [ Q ] Quit".center(WIDTH))
|
|
258
|
+
print("[ C ] Settings and Modes".center(WIDTH))
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
#playing the lyrics
|
|
263
|
+
|
|
264
|
+
def lyrics_player():
|
|
265
|
+
global playing
|
|
266
|
+
global current_lyric, current_time, progress_bar
|
|
267
|
+
global WIDTH
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
last_lyric = ""
|
|
271
|
+
|
|
272
|
+
while True:
|
|
273
|
+
new_width = shutil.get_terminal_size().columns
|
|
274
|
+
|
|
275
|
+
if new_width != WIDTH:
|
|
276
|
+
WIDTH = new_width
|
|
277
|
+
os.system("cls")
|
|
278
|
+
draw_ui()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
if playing and not pygame.mixer.music.get_busy():
|
|
282
|
+
if shuffle_mode:
|
|
283
|
+
play_next_shuffle()
|
|
284
|
+
else:
|
|
285
|
+
playing = False
|
|
286
|
+
|
|
287
|
+
if playing:
|
|
288
|
+
|
|
289
|
+
current_time = pygame.mixer.music.get_pos() / 1000
|
|
290
|
+
|
|
291
|
+
progress = current_time / song_length
|
|
292
|
+
filled = int(progress * 40)
|
|
293
|
+
progress_bar = "[" + "=" * filled + ">" + "-" * (39 - filled) + "]"
|
|
294
|
+
print(f"\033[13;1H\033[2K", end="")
|
|
295
|
+
print(progress_bar.center(WIDTH), end="", flush=True)
|
|
296
|
+
|
|
297
|
+
current_lyric = ""
|
|
298
|
+
|
|
299
|
+
for lyric_time, lyric in Lyrics:
|
|
300
|
+
if current_time >= lyric_time:
|
|
301
|
+
current_lyric = lyric
|
|
302
|
+
else:
|
|
303
|
+
break
|
|
304
|
+
if current_lyric != last_lyric:
|
|
305
|
+
|
|
306
|
+
print(f"\033[11;1H\033[2K", end="")
|
|
307
|
+
print(current_lyric.center(WIDTH), end="", flush=True)
|
|
308
|
+
last_lyric = current_lyric
|
|
309
|
+
|
|
310
|
+
show_time= format_time(current_time)
|
|
311
|
+
print(f"\033[15;1H\033[2K", end="")
|
|
312
|
+
print(f"{show_time} / {total_time}".center(WIDTH), end="", flush=True)
|
|
313
|
+
time.sleep(0.1)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def command_win():
|
|
317
|
+
global playing,music_folder,songs,current_song_index,shuffle_mode,command_mode,selected_song
|
|
318
|
+
os.system("cls")
|
|
319
|
+
|
|
320
|
+
print("exPlayer Commands")
|
|
321
|
+
print("-----------------")
|
|
322
|
+
print('type "help" for available commands')
|
|
323
|
+
print('type "back" to return to player')
|
|
324
|
+
print()
|
|
325
|
+
|
|
326
|
+
while True:
|
|
327
|
+
commands = input('eXPlayer> ')
|
|
328
|
+
if commands == "help":
|
|
329
|
+
print("Available commands:")
|
|
330
|
+
print("help - Show available commands")
|
|
331
|
+
print("about - info about eXplayer and how to use it")
|
|
332
|
+
print("back - Return to the player")
|
|
333
|
+
print('cd "[path]" - Select the music folder')
|
|
334
|
+
print('ls - show available files in the folder')
|
|
335
|
+
print('select "[song name]"- select individual song to play')
|
|
336
|
+
print('shuffle - toggle shuffle on and off (normally on)')
|
|
337
|
+
print()
|
|
338
|
+
|
|
339
|
+
elif commands == "about":
|
|
340
|
+
print("eXPlayer")
|
|
341
|
+
print("made by Aritro Halder")
|
|
342
|
+
print("version: 1.0.0")
|
|
343
|
+
print("=====================")
|
|
344
|
+
print()
|
|
345
|
+
print("How to Play Music")
|
|
346
|
+
print("=====================")
|
|
347
|
+
print("To play song you first have to change the directory using command 'cd' to the folder containing your musics. " \
|
|
348
|
+
"then go back to the player using command 'back' and you can play musics by pressing [ P ] on your keyboard")
|
|
349
|
+
print()
|
|
350
|
+
print("How to add lyrics")
|
|
351
|
+
print("=====================")
|
|
352
|
+
print("To add lyrics to a song. first create a '.lrc' file and copy and paste the lyrics with timestamps in the lrc file. " \
|
|
353
|
+
"rename the file with the same name as your song or '.mp3' and save the both file in the same folder." \
|
|
354
|
+
"the player will automatically find the lyrics file")
|
|
355
|
+
print()
|
|
356
|
+
print("Additional Info")
|
|
357
|
+
print("=====================")
|
|
358
|
+
print("The player saves your seleted file path in a config.json file.when shuffle is on, pressing N or B to select previous and next song will play next song randomly")
|
|
359
|
+
print("")
|
|
360
|
+
print("leave feedback in my insta @arthi_bsa_studio or leave an email in: studiozzzz033@gmail.com")
|
|
361
|
+
|
|
362
|
+
elif commands == "back":
|
|
363
|
+
command_mode = False
|
|
364
|
+
os.system("cls")
|
|
365
|
+
draw_ui()
|
|
366
|
+
break
|
|
367
|
+
|
|
368
|
+
elif commands.startswith("cd "):
|
|
369
|
+
folder = commands[3:].strip('"')
|
|
370
|
+
|
|
371
|
+
if os.path.isdir(folder):
|
|
372
|
+
music_folder = os.path.abspath(folder)
|
|
373
|
+
save_config()
|
|
374
|
+
selected_song = None
|
|
375
|
+
current_song_index = 0
|
|
376
|
+
|
|
377
|
+
print()
|
|
378
|
+
print(f"Music folder changed to: {music_folder}")
|
|
379
|
+
|
|
380
|
+
else:
|
|
381
|
+
print("Directory not found")
|
|
382
|
+
|
|
383
|
+
elif commands == "ls":
|
|
384
|
+
list_folder()
|
|
385
|
+
|
|
386
|
+
elif commands == "shuffle":
|
|
387
|
+
shuffle_mode = not shuffle_mode
|
|
388
|
+
save_config()
|
|
389
|
+
|
|
390
|
+
if shuffle_mode:
|
|
391
|
+
print()
|
|
392
|
+
print("Shuffle mode: ON")
|
|
393
|
+
print()
|
|
394
|
+
else:
|
|
395
|
+
print()
|
|
396
|
+
print("Shuffle mode: OFF")
|
|
397
|
+
print()
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
elif commands.startswith("select "):
|
|
401
|
+
song = commands[7:].strip('"')
|
|
402
|
+
song_path = os.path.join(music_folder, song)
|
|
403
|
+
|
|
404
|
+
if os.path.isfile(song_path) and song.lower().endswith(".mp3"):
|
|
405
|
+
|
|
406
|
+
songs = get_songs()
|
|
407
|
+
|
|
408
|
+
if song_path in songs:
|
|
409
|
+
current_song_index = songs.index(song_path)
|
|
410
|
+
|
|
411
|
+
select_song(song_path)
|
|
412
|
+
|
|
413
|
+
print()
|
|
414
|
+
print(f"Selected: {song_name}")
|
|
415
|
+
print()
|
|
416
|
+
|
|
417
|
+
else:
|
|
418
|
+
print()
|
|
419
|
+
print("Song not found")
|
|
420
|
+
print()
|
|
421
|
+
|
|
422
|
+
else:
|
|
423
|
+
print(f"Unknown command: {commands}")
|
|
424
|
+
print('type "help" for available commands')
|
|
425
|
+
print('type "back" to return to player')
|
|
426
|
+
print()
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
# thread loading
|
|
430
|
+
|
|
431
|
+
def main ():
|
|
432
|
+
global playing
|
|
433
|
+
os.system("cls")
|
|
434
|
+
pygame.mixer.init()
|
|
435
|
+
|
|
436
|
+
load_config()
|
|
437
|
+
|
|
438
|
+
print("\033[2J\033[H", end="")
|
|
439
|
+
draw_ui()
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
thread = threading.Thread(target=lyrics_player,daemon=True)
|
|
443
|
+
thread.start()
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
# music control
|
|
447
|
+
|
|
448
|
+
while True:
|
|
449
|
+
|
|
450
|
+
if msvcrt.kbhit():
|
|
451
|
+
key = msvcrt.getwch()
|
|
452
|
+
|
|
453
|
+
if key == "p":
|
|
454
|
+
if selected_song:
|
|
455
|
+
pygame.mixer.music.play()
|
|
456
|
+
playing = True
|
|
457
|
+
else:
|
|
458
|
+
if not songs:
|
|
459
|
+
print(
|
|
460
|
+
"No music folder selected. Press [ C ] and use: cd \"[path]\"".center(WIDTH),end="",flush=True)
|
|
461
|
+
elif shuffle_mode:
|
|
462
|
+
play_next_shuffle()
|
|
463
|
+
playing = True
|
|
464
|
+
else:
|
|
465
|
+
select_first_song()
|
|
466
|
+
playing = True
|
|
467
|
+
|
|
468
|
+
elif key == "o":
|
|
469
|
+
pygame.mixer.music.pause()
|
|
470
|
+
playing = False
|
|
471
|
+
|
|
472
|
+
elif key == "r":
|
|
473
|
+
pygame.mixer.music.unpause()
|
|
474
|
+
playing = True
|
|
475
|
+
|
|
476
|
+
elif key == "s":
|
|
477
|
+
pygame.mixer.music.stop()
|
|
478
|
+
playing = False
|
|
479
|
+
|
|
480
|
+
elif key == "c":
|
|
481
|
+
pygame.mixer.music.pause()
|
|
482
|
+
playing = False
|
|
483
|
+
command_win()
|
|
484
|
+
|
|
485
|
+
elif key == "n":
|
|
486
|
+
play_next()
|
|
487
|
+
|
|
488
|
+
elif key == "b":
|
|
489
|
+
play_previous()
|
|
490
|
+
|
|
491
|
+
elif key == "q":
|
|
492
|
+
pygame.mixer.music.stop()
|
|
493
|
+
pygame.mixer.quit()
|
|
494
|
+
os.system("cls")
|
|
495
|
+
break
|
|
496
|
+
|
|
497
|
+
time.sleep(0.05)
|
|
498
|
+
|
|
499
|
+
if __name__ == "__main__":
|
|
500
|
+
main()
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: explayer
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A terminal-based music player with synchronized LRC lyrics
|
|
5
|
+
Author-email: Aritro Halder <studiozzzz033@gmail.com>
|
|
6
|
+
License: Copyright 2026 Aritro Halder
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE.txt
|
|
16
|
+
Requires-Dist: pygame>=2.6.0
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# eXPlayer
|
|
20
|
+
eXPlayer is a terminal based music player with lyrics support. The player includes a command window to change settings and other functions!
|
|
21
|
+
|
|
22
|
+
# Screenshot and videos
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE.txt
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
explayer/__init__.py
|
|
5
|
+
explayer/player.py
|
|
6
|
+
explayer.egg-info/PKG-INFO
|
|
7
|
+
explayer.egg-info/SOURCES.txt
|
|
8
|
+
explayer.egg-info/dependency_links.txt
|
|
9
|
+
explayer.egg-info/entry_points.txt
|
|
10
|
+
explayer.egg-info/requires.txt
|
|
11
|
+
explayer.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pygame>=2.6.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
explayer
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "explayer"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
authors = [{name = "Aritro Halder", email = "studiozzzz033@gmail.com"}]
|
|
9
|
+
description = "A terminal-based music player with synchronized LRC lyrics"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
license = { file = "LICENSE.txt" }
|
|
13
|
+
|
|
14
|
+
dependencies = [
|
|
15
|
+
"pygame>=2.6.0"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
explayer = "explayer.player:main"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools]
|
|
22
|
+
packages = ["explayer"]
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.package-data]
|
|
25
|
+
explayer = ["config.json"]
|
explayer-1.0.0/setup.cfg
ADDED