LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - Newbie (http://www.linuxquestions.org/questions/linux-newbie-8/)
-   -   Autofill iPod Shuffle with a certain amount of megabytes of music (http://www.linuxquestions.org/questions/linux-newbie-8/autofill-ipod-shuffle-with-a-certain-amount-of-megabytes-of-music-806217/)

thiemster 05-05-2010 10:26 PM

Autofill iPod Shuffle with a certain amount of megabytes of music
 
I have a first generation iPod Shuffle (yes, I know it's old) with 512 megabytes of space (feel free to laugh). I also have arch linux installed on my computer, with a music folder containing about 30 folders full of music. This totals to about 2 gigabytes of music.

What I want to do is: create a script that moves no more than 512 megabytes (it can be slightly under this amount, but NOT over) of random songs from my music folder (and it's subsequent artist/album folders) to a separate folder. I will then use gtkpod to move those random songs in the new folder to my iPod Shuffle.

The problem is, I have no clue how to do this. Help is appreciated, either in suggesting a linux command that might do this or writing out a whole script. Either way, I would be VERY satisfied ;)

David the H. 05-07-2010 09:20 AM

Dunno why, but I found this an interesting challenge. So much so that I actually went and wrote a whole script for it. You'll have to modify it as necessary for your needs. It's fully commented so it should be rather self-explanatory.
Code:

#!/bin/bash

#break strings on newlines only
#needed if filenames could contain spaces.
IFS=$'\n' 

#what directory should you load from?
musicdir=$HOME/music_directory

#what file types do you want to load?
#comma separated list, without periods
types=mp3,aac,mp4

#maximum amount of files to be copied, in megabytes
max=512

#convert types format for use in the find command
types=${types//,/\\|}

#convert max number to bytes
max2=$max
max=$(( max * 1024 * 1024 ))

#load an array with all available files
declare -a filelist
filelist=( $(find $musicdir -type f -iregex ".+\.\(${types}\)" -print) )

#get total number of files
tnum=${#filelist[@]}

#grab random files until size total equals max amount.
#save the result in the variable "$exportlist".

declare total=0
declare exportlist
while true; do

    #get filename of random index value
    rand=$(( $RANDOM % $tnum ))
    file="${filelist[$rand]}"

    #if filename is already in the list, skip it
    [[ "$exportlist" =~ "$file" ]] && continue

    #increment the total size count
    filesize=$(stat -c %s $file)
    total=$(( total + filesize ))

    #if total size exceeds max, exit loop before copying
    #but gotta remove the last file's size again first
    [[ $total -gt $max ]] && total=$(( total - filesize )) && break

    #otherwise add the file to the exportlist,
    #inserting a newline in front of it as a separator
    exportlist+=${file/#/$'\n'}
   
    unset rand file filesize

done

#Output display

echo "Loading approximately $max2 megabytes from $tnum files."

#enable these if you want to display a verbose list.
#(looping through list confirms that each entry is separate.
#also allows reducing it to just the basename for legibility)
#echo "=========="
#for file in $exportlist; do echo "${file##*/}" ; done
#echo "=========="

echo "Files total approximately $(( total / 1024 / 1024 )) megabytes."
[[ $total -gt $max ]] && echo "Maximum size exceeded!"
#The above line should never show.

#The command to copy the files goes here.
#(but commented out for safety)

#cp -v -t /temp/ipod $exportlist

exit 0

Of course, now that I did this, some expert will probably come along and show us how to do it in two lines. :)

Edit: Hold on, just discovered a fairly big mistake. It won't properly handle duplicate entries. I'll post the revised version when I'm done.

Edit2: I've updated the script to be able to handle duplicates. At least I hope. I'd appreciate having any mistakes pointed out.

Edit3: Fixed a couple more things. It was still outputting duplicates, until I quoted the variables in the test. Now it seems to be working ok, but it's hard to test it when the input is random.

Edit4: Corrected another bug. See my follow-up post below for details.

thiemster 05-07-2010 03:06 PM

I modified it for my specific folders, AND IT WORKED!
Thank you very much. This happens to be (obviously) exactly what I was looking for!
You have inspired me to try to figure out how to do this in python (the only programming language I have at least an intermediate grasp on), and I will post the results of that whenever I figure it out.

David the H. 05-07-2010 10:34 PM

Glad to help out. I rather enjoy working out scripting challenges like this. I really just knocked it out fairly quickly though. I'm sure it could be improved with some thought.

I don't know much about python myself, but I'm sure the basic pattern of the script should be translatable. Looking forward to seeing what you come up with.

David the H. 05-09-2010 08:01 AM

Eyballing my script again, I just noticed another small bug. This line needs to be changed:
Code:

#get total number of files
tnum=$(( ${#filelist[@]} + 1 ))

It needs to read simply
Code:

tnum=${#filelist[@]}
I'd gotten myself a bit confused over the output of the above string and using it in the random number calculation later on. The ($RANDOM % $tnum) calculation outputs a number between 0 and $tnum-1, so I mistakenly thought I needed to add one in order to get it to work correctly, but since arrays start at index 0, no incrementation is actually necessary, and it also affects the output at the upper and lower ends of the range. Not to mention that I shouldn't have added it where I did in any case, since it also changes the number of files displayed in the output.

I've edited the original post to reflect this fix.

David the H. 05-09-2010 08:55 AM

I decided to make one more modification to the script, and this time I'll post the whole thing again.

I got to thinking about how there often seems to be 4-5MB of wasted space at the end of the cycle, so I've added a routine to try to fill that space in. It's really just a test that forces it to iterate a few more times before giving up. If the space is large enough then it might just find one last track to squeeze into it.

In my tests it seems to reduce the wasted space to about 2MB or so usually. You can adjust how many times you want it to keep trying.

Code:

#!/bin/bash

IFS=$'\n'  #separate on newlines only; needed if filenames could contain spaces.

#what directory should you load from?
musicdir=$HOME/music_directory

#what file types do you want to load?
#comma separated list, without periods
types=mp3,aac,mp4

#maximum amount of files to be copied, in megabytes
max=512

#convert types format for use in the find command
types=${types//,/\\|}

#convert max number to bytes
max2=$max
max=$(( max * 1024 * 1024 ))

#load an array with all available files
declare -a filelist
filelist=( $(find $musicdir -type f -iregex ".+\.\(${types}\)" -print) )

#get total number of files
tnum=${#filelist[@]}

#grab random files until size total equals max amount.
#save the result in the variable "$exportlist".

#set the first number below to tell it
#how many times to try to fill in the final gap.
declare -i tryagain=20
declare -i total=0
declare exportlist
while true; do

    #get filename of random index value
    rand=$(( $RANDOM % $tnum ))
    file="${filelist[$rand]}"

    #if filename is already in the list, skip it
        #it seeems you must quote the variables here
    [[ "$exportlist" =~ "$file" ]] && continue

    #increment the total size count
    filesize=$(stat -c %s $file)
    total=$(( total + filesize ))

    #if total size exceeds max
    #skip the file and remove it's size from the total
        if [[ $total -gt $max ]]; then

                total=$(( total - filesize ))

                #continue trying until $tryagain equals zero, then exit
                if [[ $tryagain -gt 0 ]] ; then
                        tryagain=$(( tryagain - 1 ))
                        continue
                else
                        break
                fi
        fi

    #otherwise add the file to the exportlist, adding a newline as a spacer
    exportlist+=${file:+$'\n'}${file}

    unset rand file filesize

done

#Output display

echo "Loading approximately $max2 megabytes from $tnum files."

#enable these if you want to display a verbose list.
#(looping through list confirms that each entry is separate.
#also allows reducing it to just the basename for legibility)
#finally, I've added a sort to the list.

#echo "=========="
#for file in $exportlist; do echo "${file##*/}" ; done | sort
#echo "=========="

echo "Files total approximately $(( total / 1024 / 1024 )) megabytes."
[[ $total -gt $max ]] && echo "Maximum size exceeded!"
#The above line should never show.

#The command to copy the files goes here.
#(but commented out for safety)

#cp -v -t /home/david/test/test $exportlist

exit 0


thiemster 05-18-2010 08:34 AM

Thank you very much for the script and all of the nice modifications. I unfortunately gave up on the python script, as it was just basically going to use "os.system(command)" to run all of the commands in your script. This would make it fairly pointless...


All times are GMT -5. The time now is 07:59 PM.