Here's an example Bounty programming mini-project spec:
Language: BASH script
Concept overview: Many people have .OGG/.MP3 collections of files on their hard drives. This Bounty is to write a shell script to recursively index those collections of songs and to generate standard *.M3U files.
Assumptions: The big assumption is a file system hierarchy of music based on this configuration:
...music (top-level subdirectory containing the music tree)
|
+-- genre1 (this would be something like "rock" or "rap")
|
+-- genre2 (another music genre, etc.)
|
+-- artist1 (the name of a musician/band)
|
+-- artist2 (the name of another artist, etc.)
|
+-- album1 (the name of a CD or album for this artist)
|
+-- album2 (another CD...)
Function: The shell script written should be able to be run from any subdirectory in the above music tree. If run from "music" above, the script would create .M3U files through the whole tree. Or if run from "artist1" it would only create .M3U files from that point and below.
To make it simple and clear, the individual .M3U files should be named after the subdirectory that it's in. So using the above example we would have .M3U files named "music.m3u" containing all the music below that level, "genre1.m3u" containing all the music below that level, "artist2.m3u" containing all the music below that level, "album1.m3u" containing all the music below that level, etc.
Add'l Requirements: Profuse code comments explaining what each variable name and subroutine does. Use verbose variable names.
For example, when doing a "for loop" such as:
for f in ...
Don't use "f" for the filename variable, use "FileName" or something descriptive. Remember, you're writing this for newbies.
The .M3U files generated should index all popular music extensions, both upper and lower case.
Bonus Points: Since the above shouldn't be too hard, would you like to do a little more? How about a couple of commandline switches to do things like convert "spaces" to underscores, or to remove and/or convert characters that need escaping by the shell?
Or perhaps rewrite the same program in PERL or Python to show the similarities and/or different language features?
buildm3u
buildm3u
#!/bin/bash
# Usage: ./buildm3u
# buildm3u will build an m3u playlist for an entire directory tree.
# It will place an m3u file in each subdirectory containing music files.
# The paths to the music files are full paths.
#
# Thanks to Mendel Cooper for writing the "Advanced Bash-Scripting Guide"
# Thanks to IntnsRed (creator of DebianHelp.org) for calling for this functionality.
IFS=$'\n' # Input Field Separator
# Normally this internal bash variable includes
# any type of whitespace, but we want to avoid
# splitting up file names with spaces.
# \n means "new line".
M3Ulist="`pwd`/M3UfileList.txt" # Look here to see where your
# M3U files were created.
# The backticks tell bash to execute
# the pwd command and insert its output.
# Since pwd returns the current working
# directory, the file M3UfileList.txt
# will be created in the directory from
# which buildm3u is launched (not necessarily
# the directory where buildm3u resides).
rm -f $M3Ulist # Remove this file first.
# indexCurrDir is a function.
# I only call this function once,
# but I didn't know that would be the
# case when I started writing this script.
# Still it never hurts to group some core
# functionality into a function.
#
# indexCurrDir finds all music files in the current
# working directory (including those in subdirectories).
# The full paths to the files are written to an m3u file
# named after the current working directory.
indexCurrDir ()
{
FileList="" # initialize empty variable FileList
for FileTypes in "ogg" "mp3" "flac" "wav" # loop over file types.
# the user can add file types here.
# FileTypes is a variable that cycles
# through ogg, mp3, flac, and wav
# inside the for loop.
do # the next line of a for loop is do (bash syntax for a for loop)
FindFiles=$(find $(pwd) -type f -iname "*.$FileTypes" | sort)
# $(command) is equivalent to `command` (another way to do command substitution)
# This is mainly useful because you can have nested command substitutions using $(),
# which does not work with backquotes.
# The above command finds files with an extension corresponding to
# the the value of $FileType (FileType="ogg" the first time through, then "mp3",
# then "flac", then "wav"). In bash, $ preceding a variable gets the value
# of the variable. The results of the find command is piped to sort so that
# your songs play in a sorted order (which is hopefully the intended order).
# "i" in -iname specifies that the file types are case insensitive.
FileList=$FileList$FindFiles
# Each time through the for loop, the files found are appended to the list of files.
# After exiting the loop, you have a single list of files for all extensions.
# The find command above puts a \n (new line) after each file, so you are
# building up a list of files with one file per line.
done # end the loop with done (bash syntax for a for loop)
if [ "${#FileList}" != "0" ] # do not write m3u file if file list is empty
# The ${} is the most fool proof way of accessing
# the value of a variable. The # before #FileList
# means "get the length of the variable FileList".
# Note that this means the total number of characters
# summed over all file paths.
# FileList is one big long variable, not an array.
# Before I read about the IFS=$'\n' trick,
# I was thinking of dumping the file names to a file, and
# then reading them back into an array.
then
CurrDir=$(pwd) # get the current directory
echo "$CurrDir" # Show the user where the script is finding music files.
m3uName=$(basename $CurrDir) # basename is a command that strips off the parent
# directories of a path.
echo "Writing m3u playlist." # Tell the user that an m3u playlist is being created
echo "$FileList" > "${m3uName}.m3u" # write the list of music files to the m3u file
echo "$CurrDir/${m3uName}.m3u" >> "$M3Ulist" # Add the m3u file to the list of m3u files.
# I have music scattered about,
# and need to be reminded where the music is!
fi
}
# This is the body of the script (like Main in a C program)
AllDirs=$(find $(pwd) -type d | sort) # Get a complete list of full paths to subdirectories,
# including the top level directory. This list
# includes directories without music. They are skipped
# inside the indexCurrDir function if no music is found.
for Directory in $AllDirs # loop over the top level directory and all subdirectories
# This didn't have to be recursive because the find command does the work.
do
cd "$Directory" # cd to each directory in turn
indexCurrDir # Here, the function indexCurrDir is called
# inside each directory. If there is music inside $Directory
# (or in the subdirectories of $Directory),
# then an m3u file for that directory will be created in $Directory.
done
exit 0 # an exit value of zero in bash means a successful exit
syntax highlighted by Code2HTML, v. 0.9.1
Re: buildm3u
Excellent work,
way better than my weekend work which involved using a tool first to create the playlists
and some 5 line script to read through the directories and sort and rename each playlist :)
It works great in cygwin too which i now run to create some playlists on my phone so my car can play them
through USB in the right order.
....
....
Ok I ran the script (using cygwin) and it creates m3ufiles in each directory but I guess I have to strip some
pathnames from it for it to work correctly in my player.
I'm pretty sure my car won't be able to deal with:
$ cat Nil\ Recurring.m3u
/cygdrive/f/Music/Porcupine Tree/Nil Recurring/01 - Nil Recurring.mp3
/cygdrive/f/Music/Porcupine Tree/Nil Recurring/02 - Normal.mp3
/cygdrive/f/Music/Porcupine Tree/Nil Recurring/03 - Cheating The Polygraph.mp3
/cygdrive/f/Music/Porcupine Tree/Nil Recurring/04 - What Happens Now.mp3
I'll figure it out somehow though :)
thanks again for this bounty thing...
Re: buildm3u
Glad you liked it. That means at least two people have used it, you and me :-).
Re: buildm3u
I know this thread is old but came across it trying to fine tune my own script. I wanted M3U files only in the directories with music and did not want the full path included. This is accomplished by changing the find command from "FindFiles=$(find $(pwd) -type f -iname "*.$FileTypes" | sort)" to "FindFiles=$(find -maxdepth 1 -type f -iname "*.$FileTypes" -printf '%f\n'| sort)".
Thanks,
-Chad
Re: Bounty: A recursive .M3U index generating shell script
k by playing around I manged to get it to create lots of m3u files. Great.
But I couldn't work out how to get it to append http://192.168.1.23/ to the beginning of each file name - this is needed as I really want to share these tunes on a LAN.
in the m3u files are lines like this
/var/www/fuzztunes/world/latin/SolarSystem/primera edition/SolarSystem_primera edition_01.mp3
/var/www/fuzztunes/world/latin/SolarSystem/primera edition/SolarSystem_primera edition_02.mp3
but I need to get lines like this
http://192.168.1.23/fuzztunes/world/latin/SolarSystem/primera edition/SolarSystem_primera edition_01.mp3
http://192.168.1.23/fuzztunes/world/latin/SolarSystem/primera edition/SolarSystem_primera edition_02.mp3
I guess this would take some manipulation of this line.
do # the next line of a for loop is do (bash syntax for a for loop)
FindFiles=$(find $(pwd) -type f -iname "*.$FileTypes" | sort)
The goal being to manipulate pwd to add the ip and take off the /var/www/
This is beyond my skills. I figured out enough to work out pwd awk rev and sed are commands which might help.
I'm hoping someone might be able to help out - as this I"m sure would be useful to others too.
Thanks
Mick
Re: Bounty: A recursive .M3U index generating shell script
I've never been real successful at mastering regular expressions myself,
so instead I use the rpl command (the package is also called rpl).
First run the buildm3u script without modification.
Then run
find /var/www -name '*.m3u' | xargs rpl '/var/www/' 'http://192.168.1.23'
Re: Bounty: A recursive .M3U index generating shell script
Oops, that was almost right. I had an extra trailing slash, and the command should read:
find /var/www -name '*.m3u' | xargs rpl '/var/www' 'http://192.168.1.23'