mad-play 0.1.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,67 @@
1
+ Metadata-Version: 2.3
2
+ Name: mad-play
3
+ Version: 0.1.0
4
+ Summary: Play music in the terminal like a divine madman!
5
+ Requires-Python: >=3.13
6
+ Description-Content-Type: text/markdown
7
+
8
+ # Mad Play
9
+
10
+ **Play your music from the terminal like a Divine Madman!**
11
+
12
+ [![PyPI - Version](https://img.shields.io/pypi/v/-.svg)](https://pypi.org/project/mad-play)
13
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/-.svg)](https://pypi.org/project/mad-play)
14
+
15
+ -----
16
+
17
+ ## Installation
18
+
19
+ ```console
20
+ pip install mad_play
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ For ease of use you could add an alias to your `.bashrc`, for example:
26
+
27
+ ```bash
28
+ alias = mp="mad-play"
29
+ ```
30
+
31
+ Remember to close and re-open your terminal or run:
32
+
33
+ ```bash
34
+ source ~/.bashrc
35
+ ```
36
+
37
+ Then open your terminal where the music is. To play a file:
38
+
39
+ ```bash
40
+ mp funkytown.mp3
41
+ ```
42
+
43
+ If you have a list of filenames in a text file, one filename per line and you have given it the .playlist extension, you can start playing it by:
44
+
45
+ ```bash
46
+ mp bebop.playlist
47
+ ```
48
+
49
+ Or just play every audio file in a folder like so:
50
+
51
+ ```bash
52
+ mp mongolian/
53
+ ```
54
+
55
+ press `space` to pause and resume, `n` for next tune, `b` to go back and `q` to quit.
56
+
57
+ ## Project scope
58
+
59
+ Dead simple terminal audio player that plays audio files from anywhere on your computer, with playlists edited as plain text files and the ability to play everything in a folder. The player has intuitive controls and does not require learning exotic key-bindings.
60
+
61
+ ## Contribute
62
+
63
+ This is a fun, simple project, but very useful all the same. It's a single python file with code that is fairly easy to catch. Mind you, it's early days and everything very raw. The Divine Madman invites you all to tinker, suggest improvements within the scope of the project and contribute improvements if doing so tickles your fancy!
64
+
65
+ ## License
66
+
67
+ `-` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -0,0 +1,60 @@
1
+ # Mad Play
2
+
3
+ **Play your music from the terminal like a Divine Madman!**
4
+
5
+ [![PyPI - Version](https://img.shields.io/pypi/v/-.svg)](https://pypi.org/project/mad-play)
6
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/-.svg)](https://pypi.org/project/mad-play)
7
+
8
+ -----
9
+
10
+ ## Installation
11
+
12
+ ```console
13
+ pip install mad_play
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ For ease of use you could add an alias to your `.bashrc`, for example:
19
+
20
+ ```bash
21
+ alias = mp="mad-play"
22
+ ```
23
+
24
+ Remember to close and re-open your terminal or run:
25
+
26
+ ```bash
27
+ source ~/.bashrc
28
+ ```
29
+
30
+ Then open your terminal where the music is. To play a file:
31
+
32
+ ```bash
33
+ mp funkytown.mp3
34
+ ```
35
+
36
+ If you have a list of filenames in a text file, one filename per line and you have given it the .playlist extension, you can start playing it by:
37
+
38
+ ```bash
39
+ mp bebop.playlist
40
+ ```
41
+
42
+ Or just play every audio file in a folder like so:
43
+
44
+ ```bash
45
+ mp mongolian/
46
+ ```
47
+
48
+ press `space` to pause and resume, `n` for next tune, `b` to go back and `q` to quit.
49
+
50
+ ## Project scope
51
+
52
+ Dead simple terminal audio player that plays audio files from anywhere on your computer, with playlists edited as plain text files and the ability to play everything in a folder. The player has intuitive controls and does not require learning exotic key-bindings.
53
+
54
+ ## Contribute
55
+
56
+ This is a fun, simple project, but very useful all the same. It's a single python file with code that is fairly easy to catch. Mind you, it's early days and everything very raw. The Divine Madman invites you all to tinker, suggest improvements within the scope of the project and contribute improvements if doing so tickles your fancy!
57
+
58
+ ## License
59
+
60
+ `-` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -0,0 +1,14 @@
1
+ [project]
2
+ name = "mad-play"
3
+ version = "0.1.0"
4
+ description = "Play music in the terminal like a divine madman!"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = []
8
+
9
+ [project.scripts]
10
+ mad-play = "mad_play.player:main"
11
+
12
+ [build-system]
13
+ requires = ["uv_build>=0.12.3,<0.13.0"]
14
+ build-backend = "uv_build"
@@ -0,0 +1,14 @@
1
+ [project]
2
+ name = "mad-play"
3
+ version = "0.1.0"
4
+ description = "Play music in the terminal like a divine madman!"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = []
8
+
9
+ [project.scripts]
10
+ mad-play = "mad_play.player:main"
11
+
12
+ [build-system]
13
+ requires = ["uv_build>=0.12.3,<0.13.0"]
14
+ build-backend = "uv_build"
File without changes
@@ -0,0 +1,5 @@
1
+ from .player import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -0,0 +1,186 @@
1
+ import os
2
+ import select
3
+ import signal
4
+ import subprocess
5
+ import sys
6
+ import termios
7
+ import tty
8
+ from pathlib import Path
9
+
10
+ AUDIO_EXTENSIONS = {
11
+ ".mp3",
12
+ ".wav",
13
+ ".ogg",
14
+ ".flac",
15
+ ".m4a",
16
+ ".aac",
17
+ }
18
+
19
+
20
+ def get_files(input_path):
21
+ path = Path(input_path).resolve()
22
+
23
+ if path.is_dir():
24
+ return sorted(
25
+ file_path
26
+ for file_path in path.iterdir()
27
+ if file_path.is_file()
28
+ and file_path.suffix.lower() in AUDIO_EXTENSIONS
29
+ )
30
+
31
+ if path.suffix.lower() == ".playlist":
32
+ return load_playlist(path)
33
+
34
+ return [path]
35
+
36
+
37
+
38
+ def load_playlist(path):
39
+ playlist_path = Path(path).resolve()
40
+ base_dir = playlist_path.parent
41
+
42
+ files = []
43
+
44
+ for line in playlist_path.read_text().splitlines():
45
+ line = line.strip()
46
+
47
+ # Ignore blank lines and comments
48
+ if not line or line.startswith("#"):
49
+ continue
50
+
51
+ file_path = Path(line)
52
+
53
+ if not file_path.is_absolute():
54
+ file_path = base_dir / file_path
55
+
56
+ files.append(file_path)
57
+
58
+ return files
59
+
60
+
61
+ def play_file(file_path, input_stream):
62
+ print(f"\nPlaying: {file_path}")
63
+
64
+ player = subprocess.Popen([
65
+ "ffplay",
66
+ "-nodisp",
67
+ "-autoexit",
68
+ "-loglevel", "quiet",
69
+ str(file_path),
70
+ ])
71
+
72
+ paused = False
73
+
74
+ try:
75
+ while player.poll() is None:
76
+ ready, _, _ = select.select([input_stream], [], [], 0.1)
77
+
78
+ if not ready:
79
+ continue
80
+
81
+ key = input_stream.read(1).lower()
82
+
83
+ if key in (" ", "space"):
84
+ if paused:
85
+ os.kill(player.pid, signal.SIGCONT)
86
+ paused = False
87
+ print("\rResumed ")
88
+ else:
89
+ os.kill(player.pid, signal.SIGSTOP)
90
+ paused = True
91
+ print("\rPaused ")
92
+
93
+ elif key == "n":
94
+ # Next song
95
+ if paused:
96
+ os.kill(player.pid, signal.SIGCONT)
97
+
98
+ player.terminate()
99
+ player.wait()
100
+ return "next"
101
+
102
+ elif key == "b":
103
+ # Previous song
104
+ if paused:
105
+ os.kill(player.pid, signal.SIGCONT)
106
+
107
+ player.terminate()
108
+ player.wait()
109
+ return "previous"
110
+
111
+ elif key == "q":
112
+ # Stop the entire playlist
113
+ if paused:
114
+ os.kill(player.pid, signal.SIGCONT)
115
+
116
+ player.terminate()
117
+ player.wait()
118
+ return "stop"
119
+
120
+ finally:
121
+ if player.poll() is None:
122
+ if paused:
123
+ os.kill(player.pid, signal.SIGCONT)
124
+
125
+ player.terminate()
126
+ player.wait()
127
+
128
+ # The song finished naturally
129
+ return "finished"
130
+
131
+
132
+ def main():
133
+ if len(sys.argv) != 2:
134
+ print(f"Usage: python3 {sys.argv[0]} <file, folder, or playlist>")
135
+ sys.exit(1)
136
+
137
+ files = get_files(sys.argv[1])
138
+
139
+ if not files:
140
+ print("No audio files found.")
141
+ return
142
+
143
+ fd = sys.stdin.fileno()
144
+ old_terminal_settings = termios.tcgetattr(fd)
145
+
146
+ try:
147
+ tty.setcbreak(fd)
148
+
149
+ terminal_settings = termios.tcgetattr(fd)
150
+ terminal_settings[3] &= ~termios.ECHO
151
+ termios.tcsetattr(fd, termios.TCSADRAIN, terminal_settings)
152
+
153
+ print("Commands: p = pause/resume, n = next, b = previous, s = stop")
154
+
155
+ index = 0
156
+
157
+ while 0 <= index < len(files):
158
+ file_path = files[index]
159
+
160
+ if not file_path.is_file():
161
+ print(f"\nSkipping missing file: {file_path}")
162
+ index += 1
163
+ continue
164
+
165
+ action = play_file(file_path, sys.stdin)
166
+
167
+ if action == "stop":
168
+ break
169
+
170
+ elif action == "previous":
171
+ if index > 0:
172
+ index -= 1
173
+ # At the first song, restart it
174
+ continue
175
+
176
+ elif action in ("next", "finished"):
177
+ index += 1
178
+
179
+ finally:
180
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_terminal_settings)
181
+ print("\nDone.")
182
+
183
+
184
+ if __name__ == "__main__":
185
+ main()
186
+