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.
@@ -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.
@@ -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,4 @@
1
+ # eXPlayer
2
+ eXPlayer is a terminal based music player with lyrics support. The player includes a command window to change settings and other functions!
3
+
4
+ # Screenshot and videos
@@ -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,2 @@
1
+ [console_scripts]
2
+ explayer = explayer.player:main
@@ -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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+