-
Notifications
You must be signed in to change notification settings - Fork 4
/
music_player.sh
440 lines (372 loc) · 13.2 KB
/
music_player.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
#!/bin/zsh
# Required dependencies: mpv youtube-dl
SOCKET_PATH=/tmp/mpvsocket
CURRENT_PLAYLIST=playlist.txt # Default playlist
# Aliases for common commands - all prefixed with 'm' to avoid conflicts
alias mx="play_song" # Music play by number/name
alias mn="next_song" # Music next
alias mp="previous_song" # Music previous
alias mt="toggle_pause" # Music toggle play/pause
alias ms="show_live_status" # Music status
alias ml="list_songs" # Music list
alias mpl="list_playlists" # Music playlist list
alias msw="switch_playlist" # Music switch playlist
alias mf="seek_forward" # Music forward 30s
alias mb="seek_backward" # Music back 30s
alias msh="shuffle_play" # Music shuffle
alias hlp="quick_help" # Quick help for aliases
alias sk="seek"
# Function to start mpv with proper options
start_mpv() {
local playlist=$1
mpv --no-video --really-quiet --input-ipc-server=${SOCKET_PATH} --playlist="$playlist" &
sleep 0.1
}
# Helper function to get a property from mpv
get_mpv_property() {
local property=$1
local cmd='{"command": ["get_property", "'
local end='"]}'
echo "$cmd$property$end" | \
socat - ${SOCKET_PATH} 2>/dev/null | \
grep -o '"data":[^,}]*' | \
cut -d':' -f2
}
# Function to list available playlists
list_playlists() {
echo "Available playlists:"
echo "Current playlist: $CURRENT_PLAYLIST"
echo "------------------------"
ls -1 *.txt
}
switch_playlist() {
local playlist_name=$1
if [ -z "$playlist_name" ]; then
echo "Please specify a playlist name"
list_playlists
return 1
fi
case "$playlist_name" in
*.txt) ;;
*) playlist_name=$playlist_name.txt ;;
esac
if [ -f "$playlist_name" ]; then
CURRENT_PLAYLIST="$playlist_name"
echo "Switched to playlist: $playlist_name"
stop_playback
else
echo "Playlist not found: $playlist_name"
list_playlists
return 1
fi
}
# Function to create a new playlist
create_playlist() {
local playlist_name=$1
if [[ -z "$playlist_name" ]]; then
echo "Please specify a playlist name"
return 1
fi
# If just the name is given without .txt, append it
if [[ ! $playlist_name =~ \.txt$ ]]; then
playlist_name="${playlist_name}.txt"
fi
if [[ ! -f "$playlist_name" ]]; then
touch "$playlist_name"
echo "Created new playlist: $playlist_name"
else
echo "Playlist already exists: $playlist_name"
return 1
fi
}
# Function to start mpv if it's not running
ensure_player_running() {
if ! pgrep -f "mpv.*${SOCKET_PATH}" > /dev/null; then
cleanup_tmp_files
awk -F' *\\| *' '{print $2}' "$CURRENT_PLAYLIST" | tr -d ' ' > /tmp/current_playlist.txt
start_mpv "/tmp/current_playlist.txt"
fi
}
# Function to list all songs with their labels and indices
list_songs() {
echo "Available songs in $CURRENT_PLAYLIST:"
awk -F' | ' '{print NR ". " $1}' "$CURRENT_PLAYLIST"
}
play_song() {
local search=$1
if [[ -z "$search" ]]; then
echo "Please provide a song label or number"
list_songs
return 1
fi
# If numeric, play that line number
if [[ "$search" =~ ^[0-9]+$ ]]; then
local url=$(awk -F' *\\| *' "NR==$search {print \$2}" "$CURRENT_PLAYLIST" | tr -d ' ')
local title=$(awk -F' *\\| *' "NR==$search {print \$1}" "$CURRENT_PLAYLIST")
else
# Otherwise search for label
local url=$(awk -F' *\\| *' "\$1 ~ /$search/ {print \$2; exit}" "$CURRENT_PLAYLIST" | tr -d ' ')
local title=$(awk -F' *\\| *' "\$1 ~ /$search/ {print \$1; exit}" "$CURRENT_PLAYLIST")
fi
if [[ -z "$url" ]]; then
echo "Song not found"
list_songs
return 1
fi
if pgrep -f "mpv.*${SOCKET_PATH}" > /dev/null; then
pkill -f "mpv.*${SOCKET_PATH}"
sleep 0.1
fi
# Instead of playing just this song, create a playlist starting from this song
if [[ "$search" =~ ^[0-9]+$ ]]; then
# If numeric, start playlist from this number
awk -F' *\\| *' -v start="$search" 'NR>=start {print $2}' "$CURRENT_PLAYLIST" | tr -d ' ' > /tmp/current_playlist.txt
# Also add the songs before it at the end for wraparound
awk -F' *\\| *' -v start="$search" 'NR<start {print $2}' "$CURRENT_PLAYLIST" | tr -d ' ' >> /tmp/current_playlist.txt
else
# If label search, find the line number first
local line_num=$(awk -F' *\\| *' "\$1 ~ /$search/ {print NR; exit}" "$CURRENT_PLAYLIST")
awk -F' *\\| *' -v start="$line_num" 'NR>=start {print $2}' "$CURRENT_PLAYLIST" | tr -d ' ' > /tmp/current_playlist.txt
awk -F' *\\| *' -v start="$line_num" 'NR<start {print $2}' "$CURRENT_PLAYLIST" | tr -d ' ' >> /tmp/current_playlist.txt
fi
echo "Playing: $title"
start_mpv "/tmp/current_playlist.txt"
echo "Use 'ms' or 'show_live_status' for live playback display"
}
# Function to play entire playlist
play_playlist() {
ensure_player_running
echo "Playlist started in background. Use other commands to control playback."
echo "Use 'ms' or 'show_live_status' for live playback display"
list_songs
}
# Cleanup function
cleanup_tmp_files() {
rm -f /tmp/current_playlist.txt /tmp/shuffled_playlist.txt /tmp/shuffled_full.txt
}
# Function to ensure cleanup happens on exit
setup_cleanup() {
trap cleanup_tmp_files EXIT
}
# Call setup_cleanup when the script is sourced
setup_cleanup
# Function to shuffle playlist
shuffle_play() {
if pgrep -f "mpv.*${SOCKET_PATH}" > /dev/null; then
pkill -f "mpv.*${SOCKET_PATH}"
sleep 1
fi
cleanup_tmp_files
# Create shuffled playlist using shuf for better randomization
awk -F' *\\| *' '{print $0}' "$CURRENT_PLAYLIST" | \
shuf | \
awk '{print NR ". " $0}' > /tmp/shuffled_full.txt
# Extract URLs
awk -F' *\\| *' '{print $2}' /tmp/shuffled_full.txt | tr -d ' ' > /tmp/shuffled_playlist.txt
echo "Starting shuffled playlist..."
start_mpv "/tmp/shuffled_playlist.txt"
echo "Current shuffle order:"
awk -F' *\\| *' '{print NR ". " $1}' /tmp/shuffled_full.txt
echo "Use 'ms' or 'show_live_status' for live playback display"
}
# Function to show live status
show_live_status() {
ensure_player_running
echo "Press q to exit live status"
echo -e "\033[?25l" # Hide cursor
# Save current terminal state
stty -echo -icanon # Set terminal to raw mode
while true; do
# Check for 'q' keypress immediately
if read -t 0.1 -k 1 input; then
if [[ "$input" == "q" ]]; then
break
fi
fi
# Clear previous lines and move cursor up
echo -en "\r\033[K" # Clear current line
echo -en "\033[2A" # Move up 2 lines
local title=$(get_mpv_property "media-title")
local position=$(get_mpv_property "time-pos")
local duration=$(get_mpv_property "duration")
# Format the display
echo -en "\rNow Playing: $title\n"
if [[ -n "$position" && "$position" != "null" && -n "$duration" && "$duration" != "null" ]]; then
printf "\r[%d:%02d / %d:%02d]" $((position/60)) $((position%60)) $((duration/60)) $((duration%60))
# Create progress bar
local width=50
local progress=$(( (position * width) / duration ))
echo -n " ["
for ((i=0; i<width; i++)); do
if ((i < progress)); then
echo -n "="
elif ((i == progress)); then
echo -n ">"
else
echo -n " "
fi
done
echo -n "]"
else
echo -en "\r[0:00 / 0:00] [ ]"
fi
done
# Restore terminal state
stty echo icanon
echo -e "\n" # Move to new line
echo -e "\033[?25h" # Show cursor
}
# Function to skip to next song
next_song() {
ensure_player_running
echo '{"command": ["playlist-next"]}' | socat - ${SOCKET_PATH} >/dev/null 2>&1
sleep 0.1
show_status
}
# Function to go to previous song
previous_song() {
ensure_player_running
echo '{"command": ["playlist-prev"]}' | socat - ${SOCKET_PATH} >/dev/null 2>&1
sleep 0.1
show_status
}
# Function to seek within current song
seek() {
local amount=$1
if [[ -z "$amount" ]]; then
echo "Please specify seek amount (e.g., +30, -10, or absolute time like 1:30)"
return 1
fi
ensure_player_running
# Handle timestamp format (mm:ss)
if [[ "$amount" =~ ^[0-9]+:[0-9]+$ ]]; then
local minutes=$(echo $amount | cut -d: -f1)
local seconds=$(echo $amount | cut -d: -f2)
local total_seconds=$((minutes * 60 + seconds))
echo "{\"command\": [\"seek\", $total_seconds, \"absolute\"]}" | socat - ${SOCKET_PATH} >/dev/null 2>&1
else
# Handle relative seeking (+/- seconds)
echo "{\"command\": [\"seek\", \"$amount\"]}" | socat - ${SOCKET_PATH} >/dev/null 2>&1
fi
sleep 0.1
show_status
}
# Quick seek shortcuts
seek_forward() {
seek "+30"
echo "Skipped forward 30 seconds"
}
seek_backward() {
seek "-30"
echo "Skipped backward 30 seconds"
}
# Function to pause/unpause
toggle_pause() {
ensure_player_running
echo '{"command": ["cycle", "pause"]}' | socat - ${SOCKET_PATH} >/dev/null 2>&1
echo "Toggled pause/play"
}
# Function to adjust volume
adjust_volume() {
local change=$1
ensure_player_running
echo "{\"command\": [\"add\", \"volume\", $change]}" | socat - ${SOCKET_PATH} >/dev/null 2>&1
echo "Volume adjusted by $change"
}
# Function to stop playback
stop_playback() {
if pgrep -f "mpv.*${SOCKET_PATH}" > /dev/null; then
pkill -f "mpv.*${SOCKET_PATH}"
echo "Playback stopped"
else
echo "No active playback to stop"
fi
}
# Function to show current playback status
show_status() {
ensure_player_running
local title=$(get_mpv_property "media-title")
local position=$(get_mpv_property "time-pos")
local duration=$(get_mpv_property "duration")
echo "Current track: $title"
if [[ -n "$position" && "$position" != "null" ]]; then
printf "Position: %d:%02d\n" $((position/60)) $((position%60))
else
echo "Position: 0:00"
fi
if [[ -n "$duration" && "$duration" != "null" ]]; then
printf "Duration: %d:%02d\n" $((duration/60)) $((duration%60))
else
echo "Duration: Unknown"
fi
}
quick_help() {
cat << 'EOF'
Quick Commands:
mx NUM/LABEL - Play song (by number or name)
mn/mp - Next/Previous song
mt - Toggle play/pause
ms - Show live status
ml - List songs
mpl - List playlists
msw NAME - Switch playlist
mf/mb - Forward/Back 30s
msh - Shuffle play
EOF
}
# Function to show all available commands
help() {
cat << 'EOF'
Music Player Commands:
Playback Control:
play_song NUMBER - Play song by number
play_song 'LABEL' - Play song by label search
play_playlist - Start playing full playlist
shuffle_play - Shuffle and play playlist
next_song - Skip to next song
previous_song - Go to previous song
toggle_pause - Pause/unpause playback
stop_playback - Stop playing
Navigation:
seek +/-N - Seek N seconds forward/backward
seek mm:ss - Seek to specific timestamp
seek_forward - Skip forward 30 seconds
seek_backward - Skip backward 30 seconds
Status and Information:
show_status - Show current track info
show_live_status - Show live updating display with progress bar
list_songs - Show all available songs
Playlist Management:
list_playlists - Show all available playlists
switch PLAYLIST - Switch to a different playlist (e.g., switch rock)
create PLAYLIST - Create a new empty playlist (e.g., create jazz)
Volume Control:
adjust_volume +/-N - Adjust volume (e.g., adjust_volume +10)
Aliases (Shortcuts):
mx NUMBER/LABEL - Play song (mx for 'music play')
mn - Next song
mp - Previous song
mt - Toggle play/pause
ms - Show live status
ml - List songs
mpl - List playlists
msw PLAYLIST - Switch playlist
mf - Forward 30s
mb - Back 30s
msh - Shuffle play
Tips:
- All playlists are .txt files in the current directory
- The live status display (ms) can be exited by pressing 'q'
- You can use partial matches for song labels (e.g., mx drak)
Examples:
ml # List available songs
mx 3 # Play the third song
mx drake_2 # Play first matching song
shuffle_play # Play playlist in random order
seek 1:30 # Jump to 1 minute 30 seconds
seek +30 # Skip forward 30 seconds
status # Show live progress bar
switch rock # Switch to rock.txt playlist
adjust_volume +10 # Increase volume
EOF
}