1

So I have written a bash script (named music.sh) for a Raspberry Pi to perform the following functions:

  • When executed, look into one single directory (Music folder) and select a random folder to look into. (Note: none of these folders here have subdirectories)
  • Once a folder within "Music" has been selected, then play all mp3 files IN ORDER until the last mp3 file has been reached
  • At this point, the script would go back to the folders in the "Music" directory and select another random folder
  • Then it would again play all mp3 files in that folder in order
  • Loop indefinitely until input from user

I have this code which does all of the above EXCEPT for the following items:

  • I would like to NOT play any other "album" that has been played before
  • Once all albums played once, then shutdown the system

Here is my code so far that is working (WITH duplicates allowed):

#!/bin/bash
    folderarray=($(ls -d /home/alphekka/Music/*/))
    for i in "${folderarray[@]}";
    do
    folderitems=(${folderarray[RANDOM % ${#folderarray[@]}]})    
        for j in "${folderitems[@]}";
        do
        echo `ls $j`
        cvlc --play-and-exit "${j[@]}"
        done
   done
exit 0

Please note that there isn't a single folder or file that has a space in the name. If there is a space, then I face some issues with this code working.

Anyways, I'm getting close, but I'm not quite there with the entire functionality I'm looking for. Any help would be greatly appreciated! Thank you kindly! :)

alphekka
  • 13
  • 7
  • 2
    Instead of tracking names and checking later if a randomly selected one has been repeated, can you randomize the array *before* starting the loop, and then just loop through that randomized list. – beroe Nov 02 '16 at 20:50

3 Answers3

4

Use an associative array as a set. Note that this will work for all valid folder and file names.

#!/bin/bash

declare -A folderarray
# Each folder name is a key mapped to an empty string
for d in /home/alphekka/Music/*/; do
  folderarray["$d"]=
done

while [[ "${!folderarray[*]}" ]]; do
  # Get a list of the remaining folder names
  foldernames=( "${!folderarray[@]}" )
  # Pick a folder at random
  folder=${foldernames[RANDOM%${#foldernames[@]}]}
  # Remove the folder from the set
  # Must use single quotes; see below
  unset folderarray['$folder']
  for j in "$folder"/*; do
    cvlc --play-and-exit "$j"
  done
done

Dealing with keys that contain spaces (and possibly other special characters) is tricky. The quotes shown in the call to unset above are not syntactic quotes in the usual sense. They do not prevent $folder from being expanded, but they do appear to be used by unset itself to quote the resulting string.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Would there be a way to allow folder/files that have spaces without causing too much of a headache? – alphekka Nov 02 '16 at 21:04
  • version 4.3.46(1)-release (x86_64-pc-linux-gnu). My shell is on Ubuntu 16.04 LTS – alphekka Nov 03 '16 at 00:11
  • I got it to work now! I copied your code again and this time it ran and didn't duplicate anything. Now why it didn't work like that before I don't know. Thank you for your help @chepner! – alphekka Nov 03 '16 at 01:03
  • Glad we got it straightened out! – chepner Nov 03 '16 at 01:05
0

One liner solution with mpv, rl (randomlines), xargs, find:

find /home/alphekka/Music/ -maxdepth 1 -type d -print0 | rl -d \0 | xargs -0 -l1 mpv
Ipor Sircer
  • 3,069
  • 3
  • 10
  • 15
  • By the time `tr` reads the input, it's too late to replace newlines with `\0`. To make this work for such files, `find` needs to output a null-delimited list of files, and `rl` would need to be able to read that. (The output of `rl` could then be piped directly to `xargs`.) – chepner Nov 02 '16 at 21:11
  • I couldn't get this to work at all... looks like it has some amazing potential though! Thanks for your thoughts! – alphekka Nov 03 '16 at 00:08
0

Here's another solution: randomize the list of directories first, save the result in an array and then play (my script just prints) the files from each element of the array

MUSIC=/home/alphekka/Music
OLDIFS=$IFS
IFS=$'\n'
folderarray=($(ls -d $MUSIC/*/|while read line; do echo $RANDOM $line; done| sort -n | cut -f2- -d' '))
for folder in ${folderarray[*]};
do
 printf "Folder: %s\n" $folder
 fileArray=($(find $folder -type f))
 for j in ${fileArray[@]};
 do
  printf "play %s\n" $j
 done
done

For the random shuffling I used this answer.

Community
  • 1
  • 1
user2314737
  • 27,088
  • 20
  • 102
  • 114
  • @alphekka In what order did you want to play the songs? – user2314737 Nov 03 '16 at 00:46
  • In order by name. The music I'm working with is named "01 abc.mp3" and so on. When I tried this it had a random order that i was playing songs. The folders were random as expected though. – alphekka Nov 03 '16 at 23:46
  • To sort by filename you can change the line `fileArray=($(find $folder -type f))` to `fileArray=($(find $folder -type f|sort))` passing the list of files through the `sort` command. – user2314737 Nov 04 '16 at 17:48