flow-twinx 0.4.0__tar.gz → 0.4.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.
Files changed (40) hide show
  1. flow_twinx-0.4.2/PKG-INFO +171 -0
  2. flow_twinx-0.4.2/README.md +157 -0
  3. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/Offline/commands.py +105 -33
  4. flow_twinx-0.4.2/flow_twinx/Offline/player.py +81 -0
  5. flow_twinx-0.4.2/flow_twinx/Online/commands.py +543 -0
  6. flow_twinx-0.4.2/flow_twinx/Online/player.py +140 -0
  7. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/Online/youtube.py +24 -13
  8. flow_twinx-0.4.2/flow_twinx/config.py +295 -0
  9. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/help_detail.py +24 -12
  10. flow_twinx-0.4.2/flow_twinx/lyrics.py +62 -0
  11. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/main.py +65 -19
  12. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/ping.py +0 -4
  13. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/tui.py +2 -1
  14. flow_twinx-0.4.2/flow_twinx/visualizer.py +127 -0
  15. flow_twinx-0.4.2/flow_twinx.egg-info/PKG-INFO +171 -0
  16. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx.egg-info/SOURCES.txt +2 -0
  17. flow_twinx-0.4.2/flow_twinx.egg-info/requires.txt +5 -0
  18. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/pyproject.toml +5 -1
  19. flow_twinx-0.4.2/requirements.txt +5 -0
  20. flow_twinx-0.4.0/PKG-INFO +0 -111
  21. flow_twinx-0.4.0/README.md +0 -102
  22. flow_twinx-0.4.0/flow_twinx/Offline/player.py +0 -72
  23. flow_twinx-0.4.0/flow_twinx/Online/commands.py +0 -402
  24. flow_twinx-0.4.0/flow_twinx/Online/player.py +0 -122
  25. flow_twinx-0.4.0/flow_twinx/config.py +0 -146
  26. flow_twinx-0.4.0/flow_twinx.egg-info/PKG-INFO +0 -111
  27. flow_twinx-0.4.0/flow_twinx.egg-info/requires.txt +0 -2
  28. flow_twinx-0.4.0/requirements.txt +0 -2
  29. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/Offline/__init__.py +0 -0
  30. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/Offline/file.py +0 -0
  31. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/Offline/youtube.py +0 -0
  32. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/Online/__init__.py +0 -0
  33. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/Online/savan.py +0 -0
  34. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/__init__.py +0 -0
  35. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/imports.py +0 -0
  36. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx/shortcuts.py +0 -0
  37. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx.egg-info/dependency_links.txt +0 -0
  38. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx.egg-info/entry_points.txt +0 -0
  39. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/flow_twinx.egg-info/top_level.txt +0 -0
  40. {flow_twinx-0.4.0 → flow_twinx-0.4.2}/setup.cfg +0 -0
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.4
2
+ Name: flow-twinx
3
+ Version: 0.4.2
4
+ Summary: A terminal music player with online and offline modes
5
+ Project-URL: Homepage, https://github.com/philast-015/flow/
6
+ Project-URL: Issues, https://github.com/philast-015/flow/issues
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: python-vlc
10
+ Requires-Dist: yt_dlp
11
+ Requires-Dist: ytmusicapi
12
+ Requires-Dist: numpy
13
+ Requires-Dist: sounddevice
14
+
15
+ # Flow
16
+
17
+ A terminal-based music player with online streaming and offline library modes.
18
+
19
+ ## Features
20
+
21
+ - **Dual-mode operation** — It will automatically detect internet and switch between online streaming and offline playback.
22
+ - **Online mode** — Search and stream audio from YouTube via `yt-dlp` and `python-vlc`
23
+ - **Offline mode** — Play local audio files with album support, search, and a liked-songs collection
24
+ - **Download** — Save tracks from YouTube to your local library with the `-d` flag
25
+ - **Repeat & shuffle** — Loop tracks n times or play in random order
26
+ - **Like/unlike** — Save favorites to a dedicated playlist
27
+ - **Tab completion** — Auto-complete commands and song names in offline mode
28
+ - **Colored TUI** — Cyan theme for online, magenta for offline, with borders and banners
29
+ - **Background play** — Play music in background and return to your shell
30
+ - **Audio-reactive bars** — Real-time spectrum analyzer with configurable width, height, and spacing
31
+ - **Synced lyrics** — Display color-coded lyrics that scroll with the song
32
+
33
+ ## Requirements
34
+
35
+ - Python 3
36
+ - [VLC](https://www.videolan.org/vlc/) media player (for `python-vlc` bindings)
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ git clone https://github.com/Philast-015/Flow.git
42
+ cd flow
43
+ uv run flow_twinx/main.py
44
+ ```
45
+
46
+ Or through pip:
47
+
48
+ ```bash
49
+ pip install flow-twinx
50
+ flow
51
+ ```
52
+
53
+ ### Note: Make sure you vlc installed or it will not play music.
54
+
55
+ ## Usage
56
+
57
+ ```bash
58
+ flow
59
+ ```
60
+
61
+ OR if you cloned the repo:
62
+
63
+ ```bash
64
+ cd flow_twinx
65
+ uv run main.py
66
+ ```
67
+
68
+ ### Flags
69
+
70
+ | Flag | Description |
71
+ | ------- | --------------------------------------------------------------------- |
72
+ | `-bg` | Play in background and exit to shell |
73
+ | `-kill` | Kill all background VLC processes |
74
+ | `-i` | Use it in help command to show detailed help |
75
+ | `-s` | Use it shuffle or play random songs |
76
+ | `-i` | Use it to repeat songs no of time [ -t n ] [ -t ] ( n = no of times ) |
77
+
78
+ ### Shell Mode
79
+
80
+ Run commands directly from your shell without entering interactive mode.
81
+ Play-like commands (`-pl`, `-rd`) automatically run in background.
82
+
83
+ ```bash
84
+ flow -pl never gonna give you up # play (auto-bg)
85
+ flow -rd daft punk # radio (auto-bg)
86
+ flow -sh daft punk # search (show results, exit)
87
+ flow -kill # kill VLC
88
+ ```
89
+
90
+ Also works with positional commands:
91
+
92
+ ```bash
93
+ flow play never gonna give you up
94
+ flow radio daft punk
95
+ flow search daft punk
96
+ ```
97
+
98
+ Shell shortcuts use your user-defined shortcuts with `-` prefix:
99
+
100
+ ```bash
101
+ flow -svn hello # svn → savan
102
+ flow -dl never gonna give you up # dl → download
103
+ ```
104
+
105
+ ### Commands
106
+
107
+ | Command | Description |
108
+ | ---------------------- | ------------------------------------------------- |
109
+ | `play <name or #>` | Play a song by name or search result number |
110
+ | `search <query>` | Search YouTube (online) or library (offline) |
111
+ | `list` | Show all songs, albums, or liked tracks |
112
+ | `like <name or #>` | Add a song to your liked collection |
113
+ | `download <name or #>` | Save a streamed song to the local library |
114
+ | `radio <name> [#]` | Generate a radio mix, or play specific track |
115
+ | `switch` | Toggle between online and offline mode |
116
+ | `help` | Show available commands |
117
+ | `help -i` | Show available commands with detailed explanation |
118
+
119
+ ## Configuration
120
+
121
+ - Downloads and library are stored in `~/.flow/downloads/`
122
+ - Liked songs (online) are saved to `~/.flow/liked.txt`
123
+ - Liked songs (offline) are copied to `~/.flow/downloads/liked songs/`
124
+ - User shortcuts are stored in `~/.flow/shortcuts.json`
125
+ - Config file: `~/.flow/config.json`
126
+
127
+ ### Config Options
128
+
129
+ | Target | Description | Range |
130
+ | ------------ | ---------------------------- | ------------------ |
131
+ | `primary` | Color for online songs | Any color |
132
+ | `secondary` | Color for offline songs | Any color |
133
+ | `tertiary` | Color for labels | Any color |
134
+ | `display` | Playback display mode | none, bars, lyrics |
135
+ | `barwidth` | Number of bars in visualizer | 4-80 |
136
+ | `barheight` | Height of bars | 2-16 |
137
+ | `barspacing` | Space between bars | 0-4 |
138
+
139
+ Usage: `config <target> <value>`
140
+
141
+ ## Project Structure
142
+
143
+ ```
144
+ flow/
145
+ ├── flow_twinx/
146
+ │ ├── __init__.py
147
+ │ ├── config.py
148
+ │ ├── imports.py
149
+ │ ├── main.py
150
+ │ ├── ping.py
151
+ │ ├── tui.py
152
+ │ ├── lyrics.py # Synced lyrics fetching
153
+ │ ├── visualizer.py # Audio-reactive bars
154
+ │ ├── Online/
155
+ │ │ ├── __init__.py
156
+ │ │ ├── commands.py
157
+ │ │ ├── player.py
158
+ │ │ └── youtube.py
159
+ │ └── Offline/
160
+ │ ├── __init__.py
161
+ │ ├── commands.py
162
+ │ ├── file.py
163
+ │ ├── player.py
164
+ │ └── youtube.py
165
+ ├── pyproject.toml
166
+ └── README.md
167
+ ```
168
+
169
+ ## License
170
+
171
+ Use however you want just mention me for inspiration.
@@ -0,0 +1,157 @@
1
+ # Flow
2
+
3
+ A terminal-based music player with online streaming and offline library modes.
4
+
5
+ ## Features
6
+
7
+ - **Dual-mode operation** — It will automatically detect internet and switch between online streaming and offline playback.
8
+ - **Online mode** — Search and stream audio from YouTube via `yt-dlp` and `python-vlc`
9
+ - **Offline mode** — Play local audio files with album support, search, and a liked-songs collection
10
+ - **Download** — Save tracks from YouTube to your local library with the `-d` flag
11
+ - **Repeat & shuffle** — Loop tracks n times or play in random order
12
+ - **Like/unlike** — Save favorites to a dedicated playlist
13
+ - **Tab completion** — Auto-complete commands and song names in offline mode
14
+ - **Colored TUI** — Cyan theme for online, magenta for offline, with borders and banners
15
+ - **Background play** — Play music in background and return to your shell
16
+ - **Audio-reactive bars** — Real-time spectrum analyzer with configurable width, height, and spacing
17
+ - **Synced lyrics** — Display color-coded lyrics that scroll with the song
18
+
19
+ ## Requirements
20
+
21
+ - Python 3
22
+ - [VLC](https://www.videolan.org/vlc/) media player (for `python-vlc` bindings)
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ git clone https://github.com/Philast-015/Flow.git
28
+ cd flow
29
+ uv run flow_twinx/main.py
30
+ ```
31
+
32
+ Or through pip:
33
+
34
+ ```bash
35
+ pip install flow-twinx
36
+ flow
37
+ ```
38
+
39
+ ### Note: Make sure you vlc installed or it will not play music.
40
+
41
+ ## Usage
42
+
43
+ ```bash
44
+ flow
45
+ ```
46
+
47
+ OR if you cloned the repo:
48
+
49
+ ```bash
50
+ cd flow_twinx
51
+ uv run main.py
52
+ ```
53
+
54
+ ### Flags
55
+
56
+ | Flag | Description |
57
+ | ------- | --------------------------------------------------------------------- |
58
+ | `-bg` | Play in background and exit to shell |
59
+ | `-kill` | Kill all background VLC processes |
60
+ | `-i` | Use it in help command to show detailed help |
61
+ | `-s` | Use it shuffle or play random songs |
62
+ | `-i` | Use it to repeat songs no of time [ -t n ] [ -t ] ( n = no of times ) |
63
+
64
+ ### Shell Mode
65
+
66
+ Run commands directly from your shell without entering interactive mode.
67
+ Play-like commands (`-pl`, `-rd`) automatically run in background.
68
+
69
+ ```bash
70
+ flow -pl never gonna give you up # play (auto-bg)
71
+ flow -rd daft punk # radio (auto-bg)
72
+ flow -sh daft punk # search (show results, exit)
73
+ flow -kill # kill VLC
74
+ ```
75
+
76
+ Also works with positional commands:
77
+
78
+ ```bash
79
+ flow play never gonna give you up
80
+ flow radio daft punk
81
+ flow search daft punk
82
+ ```
83
+
84
+ Shell shortcuts use your user-defined shortcuts with `-` prefix:
85
+
86
+ ```bash
87
+ flow -svn hello # svn → savan
88
+ flow -dl never gonna give you up # dl → download
89
+ ```
90
+
91
+ ### Commands
92
+
93
+ | Command | Description |
94
+ | ---------------------- | ------------------------------------------------- |
95
+ | `play <name or #>` | Play a song by name or search result number |
96
+ | `search <query>` | Search YouTube (online) or library (offline) |
97
+ | `list` | Show all songs, albums, or liked tracks |
98
+ | `like <name or #>` | Add a song to your liked collection |
99
+ | `download <name or #>` | Save a streamed song to the local library |
100
+ | `radio <name> [#]` | Generate a radio mix, or play specific track |
101
+ | `switch` | Toggle between online and offline mode |
102
+ | `help` | Show available commands |
103
+ | `help -i` | Show available commands with detailed explanation |
104
+
105
+ ## Configuration
106
+
107
+ - Downloads and library are stored in `~/.flow/downloads/`
108
+ - Liked songs (online) are saved to `~/.flow/liked.txt`
109
+ - Liked songs (offline) are copied to `~/.flow/downloads/liked songs/`
110
+ - User shortcuts are stored in `~/.flow/shortcuts.json`
111
+ - Config file: `~/.flow/config.json`
112
+
113
+ ### Config Options
114
+
115
+ | Target | Description | Range |
116
+ | ------------ | ---------------------------- | ------------------ |
117
+ | `primary` | Color for online songs | Any color |
118
+ | `secondary` | Color for offline songs | Any color |
119
+ | `tertiary` | Color for labels | Any color |
120
+ | `display` | Playback display mode | none, bars, lyrics |
121
+ | `barwidth` | Number of bars in visualizer | 4-80 |
122
+ | `barheight` | Height of bars | 2-16 |
123
+ | `barspacing` | Space between bars | 0-4 |
124
+
125
+ Usage: `config <target> <value>`
126
+
127
+ ## Project Structure
128
+
129
+ ```
130
+ flow/
131
+ ├── flow_twinx/
132
+ │ ├── __init__.py
133
+ │ ├── config.py
134
+ │ ├── imports.py
135
+ │ ├── main.py
136
+ │ ├── ping.py
137
+ │ ├── tui.py
138
+ │ ├── lyrics.py # Synced lyrics fetching
139
+ │ ├── visualizer.py # Audio-reactive bars
140
+ │ ├── Online/
141
+ │ │ ├── __init__.py
142
+ │ │ ├── commands.py
143
+ │ │ ├── player.py
144
+ │ │ └── youtube.py
145
+ │ └── Offline/
146
+ │ ├── __init__.py
147
+ │ ├── commands.py
148
+ │ ├── file.py
149
+ │ ├── player.py
150
+ │ └── youtube.py
151
+ ├── pyproject.toml
152
+ └── README.md
153
+ ```
154
+
155
+ ## License
156
+
157
+ Use however you want just mention me for inspiration.
@@ -1,7 +1,7 @@
1
+ import os
1
2
  import random
2
3
  from ..imports import config, merge_flags, is_connected
3
4
  from .. import shortcuts
4
- from .. import help_detail
5
5
  from . import file as lib
6
6
  from . import player
7
7
 
@@ -17,6 +17,20 @@ m = lambda t: print(f"{M}{t}{R}")
17
17
  e = lambda t: print(f"{E}{t}{R}")
18
18
  i = lambda t: print(f"{P if config.Mode == 'Online' else S}{t}{R}")
19
19
 
20
+
21
+ def _fork_bg(label):
22
+ config.kill_stored()
23
+ pid = os.fork()
24
+ if pid > 0:
25
+ config.save_pid(pid)
26
+ i(f"{label} in background (PID: {pid})")
27
+ return False
28
+ devnull = os.open(os.devnull, os.O_RDWR)
29
+ os.dup2(devnull, 0)
30
+ os.dup2(devnull, 1)
31
+ os.dup2(devnull, 2)
32
+ return True
33
+
20
34
  try:
21
35
  import readline
22
36
  _HAS_READLINE = True
@@ -77,7 +91,6 @@ def _setup_completion():
77
91
  def run(cmd: str, extra: list[str], args):
78
92
  _setup_completion()
79
93
  cmd = shortcuts.resolve(cmd)
80
- inf = "-i" in extra
81
94
  extra, args = merge_flags(extra, args)
82
95
  if cmd == "play":
83
96
  play(extra, args)
@@ -90,7 +103,7 @@ def run(cmd: str, extra: list[str], args):
90
103
  elif cmd == "switch":
91
104
  switch_mode()
92
105
  elif cmd == "help":
93
- show_help(inf)
106
+ show_help()
94
107
  elif cmd == "short":
95
108
  shortcuts.cmd_short(extra, m)
96
109
  elif cmd == "config":
@@ -137,41 +150,95 @@ def play(extra: list[str], args):
137
150
  return
138
151
 
139
152
  _last_played = song_path
153
+ if getattr(args, "bg", False):
154
+ if not _fork_bg("Now playing"):
155
+ return
140
156
  player.play_file(song_path, song_path.stem, args)
141
157
 
142
158
 
143
159
  def _play_liked(args):
160
+ global _last_played
144
161
  liked = lib.get_liked_songs()
145
162
  if not liked:
146
163
  e("No liked songs yet")
147
164
  return
148
- if getattr(args, "s", False):
149
- random.shuffle(liked)
150
- for song in liked:
151
- _last_played = song
152
- player.play_file(song, song.stem, args)
165
+ songs = list(liked)
166
+ if getattr(args, "shuffle", False):
167
+ random.shuffle(songs)
168
+ repeat = getattr(args, "repeat", False)
169
+ repeat_count = getattr(args, "repeat_count", 0)
170
+ iteration = 0
171
+ if getattr(args, "bg", False):
172
+ if not _fork_bg("Playing liked songs"):
173
+ return
174
+ try:
175
+ while True:
176
+ for song in songs:
177
+ _last_played = song
178
+ player.play_file(song, song.stem, args)
179
+ if not repeat:
180
+ break
181
+ iteration += 1
182
+ if repeat_count > 0 and iteration >= repeat_count:
183
+ break
184
+ except KeyboardInterrupt:
185
+ pass
153
186
 
154
187
  def _play_all(args):
155
- all = lib.get_all_songs()
156
- if not all:
188
+ global _last_played
189
+ all_songs = lib.get_all_songs()
190
+ if not all_songs:
157
191
  e("No downloaded songs yet")
158
192
  return
159
- if getattr(args, "s", False):
160
- random.shuffle(all)
161
- for song in all:
162
- _last_played = song
163
- player.play_file(song, song.stem, args)
193
+ songs = list(all_songs)
194
+ if getattr(args, "shuffle", False):
195
+ random.shuffle(songs)
196
+ repeat = getattr(args, "repeat", False)
197
+ repeat_count = getattr(args, "repeat_count", 0)
198
+ iteration = 0
199
+ if getattr(args, "bg", False):
200
+ if not _fork_bg("Playing all songs"):
201
+ return
202
+ try:
203
+ while True:
204
+ for song in songs:
205
+ _last_played = song
206
+ player.play_file(song, song.stem, args)
207
+ if not repeat:
208
+ break
209
+ iteration += 1
210
+ if repeat_count > 0 and iteration >= repeat_count:
211
+ break
212
+ except KeyboardInterrupt:
213
+ pass
164
214
 
165
215
  def _play_album(album: str, args):
216
+ global _last_played
166
217
  songs = lib.get_album_songs(album)
167
218
  if not songs:
168
219
  e(f"No songs found in album '{album}'")
169
220
  return
170
- if getattr(args, "s", False):
171
- random.shuffle(songs)
172
- for song in songs:
173
- _last_played = song
174
- player.play_file(song, song.stem, args)
221
+ tracks = list(songs)
222
+ if getattr(args, "shuffle", False):
223
+ random.shuffle(tracks)
224
+ repeat = getattr(args, "repeat", False)
225
+ repeat_count = getattr(args, "repeat_count", 0)
226
+ iteration = 0
227
+ if getattr(args, "bg", False):
228
+ if not _fork_bg(f"Playing album: {album}"):
229
+ return
230
+ try:
231
+ while True:
232
+ for song in tracks:
233
+ _last_played = song
234
+ player.play_file(song, song.stem, args)
235
+ if not repeat:
236
+ break
237
+ iteration += 1
238
+ if repeat_count > 0 and iteration >= repeat_count:
239
+ break
240
+ except KeyboardInterrupt:
241
+ pass
175
242
 
176
243
 
177
244
  def like_track():
@@ -223,20 +290,25 @@ def list_library():
223
290
 
224
291
  def switch_mode():
225
292
  if is_connected():
226
- pass
293
+ config.Mode = "Online"
227
294
  else:
228
295
  e("No internet connection")
229
296
 
230
297
 
231
- def show_help(inf=False):
232
- if inf:
233
- print(f"{T}Offline Commands (detailed):{R}")
234
- for cmd, lines in help_detail.OFFLINE_HELP.items():
235
- for line in lines:
236
- print(f" {line}")
237
- print()
238
- else:
239
- print(f"{T}Offline Commands:{R}")
240
- for cmd, desc in COMMANDS.items():
241
- print(f" {T}{cmd:12s}{R} {G}{desc}{R}")
242
- print(f"{G} Use 'help -i' for detailed usage{R}")
298
+ def show_help():
299
+ print(f"{T}Offline Commands:{R}")
300
+ for cmd, desc in COMMANDS.items():
301
+ print(f" {T}{cmd:12s}{R} {G}{desc}{R}")
302
+ print(f"\n{T}Config:{R}")
303
+ print(f" {T}config{R} {G}Configure colors, display mode, bars{R}")
304
+ print(f" {G}config help Show all config targets and colors{R}")
305
+ print(f" {G}config display Set display: none, bars, lyrics{R}")
306
+ print(f" {G}config primary Set primary color (online songs){R}")
307
+ print(f" {G}config secondary Set secondary color (offline songs){R}")
308
+ print(f" {G}config tertiary Set tertiary color (labels){R}")
309
+ print(f" {G}config barwidth Bar count: 4-80 (current: {config.BarWidth}){R}")
310
+ print(f" {G}config barheight Bar height: 2-16 (current: {config.BarHeight}){R}")
311
+ print(f" {G}config barspacing 0-4, min, fit, max (current: {config.BarSpacing}){R}")
312
+ print(f"\n{T}Display Modes:{R}")
313
+ print(f" {G}bars{R} Audio-reactive spectrum analyzer (needs audio output){R}")
314
+ print(f" {G}none{R} No display during playback{R}")
@@ -0,0 +1,81 @@
1
+ import sys
2
+ import time
3
+ import vlc
4
+ from ..imports import config
5
+ from .. import visualizer
6
+
7
+ P = config.Primary
8
+ S = config.Secondary
9
+ T = config.Tertiary
10
+ M = config.Muted
11
+ E = config.Red
12
+ R = config.Reset
13
+
14
+ m = lambda t: print(f"{M}{t}{R}")
15
+ e = lambda t: print(f"{E}{t}{R}")
16
+ i = lambda t: print(f"{P if config.Mode == 'Online' else S}{t}{R}")
17
+ t = lambda t: print(f"{T}{t}{R}")
18
+
19
+
20
+ def _flags_str(args):
21
+ if not args:
22
+ return ""
23
+ parts = []
24
+ if getattr(args, "repeat", False):
25
+ count = getattr(args, "repeat_count", 0)
26
+ parts.append(f"[Repeat:{'∞' if count < 0 else count}]")
27
+ if getattr(args, "shuffle", False):
28
+ parts.append("[Shuffle:On]")
29
+ return " ".join(parts)
30
+
31
+
32
+ def _display_loop(player, duration=0):
33
+ display = config.Display
34
+ if display == "none":
35
+ return
36
+
37
+ if display == "bars":
38
+ visualizer.start()
39
+
40
+ start = time.time()
41
+ try:
42
+ while player.get_state() not in (vlc.State.Ended, vlc.State.Error):
43
+ if display == "bars":
44
+ bar_str = visualizer.render(color=S, reset=R)
45
+ sys.stdout.write(f"\r{bar_str}")
46
+ sys.stdout.flush()
47
+ time.sleep(0.08)
48
+ finally:
49
+ if display == "bars":
50
+ visualizer.stop()
51
+ sys.stdout.write("\r" + " " * 60 + "\r\n")
52
+ sys.stdout.flush()
53
+
54
+
55
+ def play_file(filepath, title, args=None):
56
+ instance = vlc.Instance("--no-video --quiet")
57
+ player = instance.media_player_new()
58
+ media = instance.media_new(str(filepath))
59
+ player.set_media(media)
60
+ player.play()
61
+
62
+ duration = 0
63
+ for _ in range(50):
64
+ duration = player.get_length() / 1000
65
+ if duration > 0:
66
+ break
67
+ time.sleep(0.1)
68
+ if duration <= 0:
69
+ duration = 0
70
+ dur_min, dur_sec = divmod(int(duration), 60)
71
+ flags = _flags_str(args)
72
+ i(f"\nPlaying : {title}")
73
+ m(f" [{dur_min}:{dur_sec:02d}] {flags}" if flags else f" [{dur_min}:{dur_sec:02d}]")
74
+
75
+ try:
76
+ _display_loop(player, duration=duration)
77
+ except KeyboardInterrupt:
78
+ player.stop()
79
+ sys.stdout.write("\n")
80
+ sys.stdout.flush()
81
+ raise