Category Archives: Shell Games & Scripts

Improved Recursive Prepending Script

Not so long ago I wrote a script for prepending disc numbers before song numbers according to folder hierarchies.  You can find the original script here:

Recursive Prepending Script

The original script was designed specifically for working with FLAC files.  I recognize that not everyone has my passion for sound quality or my devotion to this particular open-source format.  As such I have updated that script so that it will accept a user-supplied file extension.  This means that the version of the script you find below may be used for any number of file types (one at a time).

Same as previously, the script assumes that your discs are separated into folders called 01, 02, and so forth.  So you will want to name your sub-folders accordingly (it will ignore folders with names such as Disc 1 or Physical Graffiti 1 or anything really that isn’t a two digit number).

Once you have renamed your disc folders accordingly, this script will begin at a folder you select and parse into that folder, seeking out all folders named with two digit numbers, and renaming all the files contained therein if they have the file extension you supplied.

Also, I like my file names to run album number dot track number (01.01 – Track Name.flac).  You could adjust the script if you prefer a different naming convention.

(Use the pointy brackets button to copy and paste.)

##
#!/bin/bash
# Prepend album number before track numbers automatically and recursively
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

## gather information

# obtain directory in which to work

echo
echo
echo "This script changes file names under a containing folder which you supply."
echo "It assumes that your files are separated into numbered sub-folders."
echo "As such sub-folder names should be 01, 02, and so forth (00-99 will work)."
echo
echo "Example:  \"/Some/Folder/Path/01/01 – Track Name.flac\" becomes \"/Some/Folder/Path/01.01 – Track Name.flac\""
echo

read -e -p "I will require the path to the containing folder (cannot be blank): "
   if [ -d "$REPLY" ]; then
      printf "\n\nI have confirmed this is a directory.\n"
      directory="$REPLY"
   else
      printf "\n\nI cannot find the directory $REPLY\n\n"
      exit 1
   fi

# set default file extension

fileextension=FLAC

# obtain file extension

echo
echo "By default this script locates and changes "$fileextension" files using case insensivity."
echo "If you would like to change different files (mp3, ape, &c), you will want to change the file extension used in the search."
echo

read -e -p "Please indicate your preferred file extension (leave blank for "$fileextension"): "
   if [ -z "$REPLY" ]; then
      echo "The default remains "$fileextension"."
   else
      fileextension="$REPLY"
   fi

echo
echo "I will now change all files within numbered (00-99) folders under the directory "$directory" if they have the file extension "$fileextension" to include the folder number followed by a dot (i.e. 01.) and move that file into its parent directory."
echo
read -p "Press <ENTER> to continue (ctrl-c will abort)."
   echo    

## do the work

# find all two-digit numerically-named sub-directories under root directory

find "$directory" -type d -name '[0-9][0-9]' > /tmp/whyme

echo

# parse disc folders into array

declare -a discfind
   let i=0
   while read discline; do
      discfind[$i]=$discline

      # extract disc number from folder path
      disc=$( basename "$discline" )
      discpath=$( dirname "$discline" ) 

      # set case insensitivity
      shopt -s nocaseglob

      # change file names in sub folder

      for filepath in "/$discpath/$disc/"*.$fileextension; do
         filename=$( basename "$filepath" )
         mv "$discpath/$disc/$filename" "$discpath/$disc.$filename"
         echo $filename changed
      done

      # unset case insensitivity
      shopt -u nocaseglob

      ((i++))
   done < /tmp/whyme

rm /tmp/whyme

exit

##

I hope this serves you well. Feel free to send me questions in the comments.

Share

Telnet and Modern Windows Systems

I have tried to set up port forwarding on my home router for ssh.  I am having some trouble connecting through the usual port 22 and wanted to test if anything could communicate across port 22.  Thus I issued the following command at the Windows 7 command prompt: telnet [home IP address] 22.

This yielded an interesting result:

‘telnet’ is not recognized as an internal or external command, operable program or batch file.

Excuse me?

Oh, apparently beginning with Vista Windows is shipped with telnet disabled.  Didn’t you get the memo?

To enable telnet is several mouse clicks easily performed:

  1. Open your Control Panel
  2. Under Programs click Turn Windows features on or off (and you may have to do that twice because Windows is so Windowy—look for it on the left the second time)
  3. Scroll down to T for Telnet Client and check that box
  4. Follow these same steps to disable it when you are finished (adding un to step 3)

This article speculates that the reason Windows now ships this way is “More than likely this was an attempt to make Windows more secure by default, as Telnet is very insecure and whenever you have the choice you should always use SSH”.  The only problem with this theory is that if you try to run ssh instead:

‘ssh’ is not recognized as an internal or external command, operable program or batch file.

That’s why I always install Cygwin on my Windows machines; it gives me a real BASh prompt and oh so many other Unix and Unix-like goodies.

Have fun with that.

Share

Pin Cygwin Like a Medal

Well I’m working in a Windows world yet again.  Of course this means I’m installing Cygwin so I have BASh and Perl and other Unix goodies at my fingertips.

I guess someone at Microsoft has been reading my blog because they decided to make 7 suck less than Vista.  Thanks, MS.

One of the interesting improvements is in the Taskbar.  You’ve probably read a lot about the replacing of my much-beloved Quick Launch with the new pinning features of the Taskbar.  I won’t bore you talking about it.

I did encounter a problem, however, in setting up Cygwin.  Since Cygwin is a binary file (a .bat file) Windows will not allow you to pin it to the Taskbar.  If you try to run the application and pin that, you will find that you have pinned a Windows Command Prompt instead of Cygwin (since Cygwin actually runs within cmd).  You have to create a special shortcut, run that shortcut, and finally pin that to your Taskbar.

There is one other modification you can make if you’d like, and that is to change the icon(the default icon is a green triangle surrounded by black and the default Taskbar is black so it rather disappears into a tiny green triangle).  You’ll have to hunt down your own icon (post it in the comments if you find a great one).  I just live with the tiny green triangle.

This is also an opportunity to change the name of the icon if you’re into that sort of thing (and again this is optional).  I called mine CygwinBash.

When you install Cygwin there will be a shortcut on your desktop.  You can use that shortcut or you can use any usual method to create another shortcut on your desktop.  Right-click on that shortcut and choose Properties.

Replace the Target with this: C:\Windows\System32\cmd.exe /c C:\cygwin\Cygwin.bat

You may now run this shortcut and pin the running application to your Taskbar.  If you’d like to change your icon first, then click the Change Icon button and locate your new icon (the default is probably at C:\cygwin\Cygwin.ico).  If you want to change your icon later you will have to begin again with a shortcut, follow these steps, and pin that new running application (with the altered icon) to the Taskbar.

I hope that helps you get your Shell on in Windows 7.

(Thanks to this article for some information.)

Share

A New Script for Skipping Tracks

A while back I wrote a simple script for skipping tracks (and a couple of other things) for Rhythmbox.  Now that Ubuntu is moving to Banshee as their default media library application I have tossed together a new script for use with Banshee.

The trouble with Rhythmbox was getting the system to understand, when attached remotely, which monitor contained Rhythmbox.  This was dealt with by making use of the $DISPLAY variable.  Banshee makes this problem a bit more difficult.  Banshee uses dbus and dbus is more difficult to guess at and more difficult to inquire.  A little research has given me a workable bit of code that gets me there.  (I am still researching this code either for better or safer or more elegant solutions.)

I have built in some logic to make the script handle situations where the user inputs a command (argument) that is not necessarily appropriate to the current state of Banshee. It should handle most of what you might (accidentally?) throw at it. I still want to give it some parsing abilities so that when it displays the currently playing information it’s more user friendly. I’ll just add that here when I get that built.

I have built a code section (in two parts) for seeking (forward and reverse). The problem is that in my version of Ubuntu there is a dependency bug (in dbus) so the seeking won’t work for Banshee from the command line. I have included it here for folks for whom this bug does not apply. Feel free to comment on your operating system and Banshee version if my code works for you.

To get the seek code working simply paste the two parts into the two places in the script where I have placed markers. (You may optionally uncomment (remove the #’s) from the two lines in the help section near the end of the script, but it’s not required for the seek code to function. If you are running Ubuntu 10.04 like me you won’t need the seek code unless you have dealt with the dbus bug yourself.)

##
#!/bin/bash
# Banshee Control Script
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

# First tell the system how to find the current running version of Banshee if you are running this script remotely.  If you only use the script locally you can comment out this export line.
# This command reports a host of permissions errors and doesn't need to display its output to the user so we divert STDERR to null.

export $(strings /proc/*/environ 2> /dev/null | grep DBUS_SESSION | tail -1)


# Now we can manipulate Banshee.
# Grab that variable...

argument=$1

## The first part of the seek code belongs here.



##



# ... and shake it!

   if [ "$argument" = "p" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
         if [ "$pbstat" = "paused" ]; then
            printme="I am resuming playback."
            banshact="banshee --play"
            banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
         elif [ "$pbstat" = "idle" ]; then
            printme="I am beginning playback."
            banshact="banshee --play"
            banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
         elif [ "$pbstat" = "playing" ]; then
            printme="I am pausing playback."
            banshact="banshee --pause"
         else
            printme="Is Banshee even on?  I'm confused." 
         fi

   elif [ "$argument" = "play" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
      if [ "$pbstat" = "idle" ]; then
         printme="I am beginning playback."
         banshact="banshee --play"
         banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
      elif [ "$pbstat" = "paused" ]; then
         printme="I am resuming playback."
         banshact="banshee --play"
         banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
      elif [ "$pbstat" = "playing" ]; then
         printme="Banshee is already playing."
      else
         printme="Is Banshee even on?  I'm confused."
      fi

   elif [ "$argument" = "pause" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
      if [ "$pbstat" = "playing" ]; then
         printme="I am pausing playback."
         banshact="banshee --pause"
      elif [ "$pbstat" = "paused" ]; then
         printme="Banshee is paused.  Use p or play to resume playback."
      elif [ "$pbstat" = "idle" ]; then
         printme="Banshee is stopped.  Use play to resume playback."
      else
         printme="Is Banshee even on?  I'm confused."
      fi

   elif [ "$argument" = "stop" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
      if [ "$pbstat" = "playing" ]; then
         printme="I am stopping playback."
         banshact="banshee --stop"
         
      elif [ "$pbstat" = "paused" ]; then
         printme="Banshee was paused and I have now stopped it."
         banshact="banshee --stop"
         
      elif [ "$pbstat" = "idle" ]; then
         printme="Banshee is already stopped."
      else
         printme="Is Banshee even on?  I'm confused."
      fi

   elif [ "$argument" = "n" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
      if [ "$pbstat" = "playing" ]; then
         printme="I am skipping to the next track."
         banshact="banshee --next"
         banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
      elif [ "$pbstat" = "paused" ]; then
         printme="Banshee is paused.  Use p or play to resume playback."
      elif [ "$pbstat" = "idle" ]; then
         printme="Banshee is stopped.  Use play to resume playback."
      else
         printme="Is Banshee even on?  I'm confused."
      fi
   

   elif [ "$argument" = "r" ]; then
      printme="I am going to rewind now.  Doing this early enough will supposedly go to the previous track."
      banshact="banshee --previous"
      banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
      
# I should be able to prettify this and account for null variables.
# what I want: You are listeing to song by so-and-so (x of y) from album [] [year] ti:me.

   elif [ "$argument" = "s" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
      if [ "$pbstat" = "playing" ]; then
         printme="This is what is currently playing:"
         banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
      elif [ "$pbstat" = "paused" ]; then
         printme="Banshee is currently paused here:"
         banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
      elif [ "$pbstat" = "idle" ]; then
         printme="Banshee is currently stopped.  Use play to resume playback."
      else
         printme="Is Banshee even on?  I'm confused."
      fi

## The second part of the seek code belongs here.



##

   elif [ "$argument" = "c" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
      printme="Banshee is currently $pbstat."

   else
      printf "\nI'm sorry.  I only understand the following arguments:\n\n"
      printf "  p     -- attempt to toggle between play and pause\n"
      printf "  r     -- rewind to beginning or skip reverse\n"
      printf "  n     -- skip to next track\n"
      printf "  s     -- show what's playing\n"
      printf "  play  -- play\n"
      printf "  pause -- pause\n"
      printf "  stop  -- stop\n"
      printf "  c     -- display the current state of Banshee\n\n"
# You may want to uncomment the next two lines if you are using the seek code.
#      printf "  ff    -- fast forward (include seconds as ff 10)"
#      printf "  rr    -- rewind (include seconds as rr 5)"
      printf "Note that the arguments do not use a hyphen.  Please try again.\n\n"
      exit
   fi


$banshact
echo
echo $printme
echo
$banshow
echo


exit

##

As you can see, I have given play/pause toggling (where banshee itself doesn’t really have it) through the clever use of an if-loop (I’ll just pat myself on the back, ok?). Also it tells you what is playing anytime you start playing something (new or resumed).

Point of interest, there is bit of code that was not working in the script:

banshee –play; banshee –query-title –query-artist –query-track-number –query-track-count –query-album –query-disc –query-year –query-duration

Thing is if you paste this into a terminal it works perfectly. The problem is that semi-colon. On the command line it’s just fine, but in the script there must be a space after the --play or the shell will interpret the argument as literally --play; and it won’t work.

So the basic usage of the script is in this format:

Bs p

I placed this script in /usr/local/bin on the hi-fi machine. I called it merely Bs. That way the script is available to all users on that machine and is easy to call up from the terminal.

Be sure to make the file executable:

sudo chmod a+x /usr/local/bin/Bs

For those who would like to add the seek functionality, here it is. You can always test it on your system to see if it works and remove it (or comment it out) if it doesn’t.

##
#!/bin/bash
# Seek functionality for Banshee Control Script
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

## This is the first part of the seek code.

seekseconds=$2
roundseek=$( printf "%0.f\n" $seekseconds )

##


## This is the second part of the seek code.

   elif [ "$argument" = "ff" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
         if [[ $roundseek =~ ^[0-9]+$ ]]; then
            currentlocation=`banshee --query-position`
            currloc=${currentlocation#* }
            roundloc=$( printf "%0.f\n" $currloc )
            seekchange=$(( roundloc + roundseek ))
               if [ "$pbstat" = "playing" ]; then
                  printme="I am advancing playback by $roundseek seconds."
                  banshact="banshee --set-position=$seekchange"
                  banshow=""
               elif [ "$pbstat" = "paused" ]; then
                  printme="Banshee is paused but I have advanced the pause location by $roundseek seconds."
                  banshact="banshee --set-position=$seekchange"
                  banshow=""
               elif [ "$pbstat" = "idle" ]; then
                  printme="Banshee is neither playing nor paused and thus I am not able to advance anything"
                  banshact=""
                  banshow=""
               else
                  printme="Is Banshee even on?  I'm confused."
                  banshact=""
                  banshow=""
               fi
         else
            printme="When using ff you must enter seconds in the format \"Bs ff 5\" in order for me to process your request."
            banshact=""
            banshow=""
         fi   
      
   elif [ "$argument" = "rr" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
         if [[ $roundseek =~ ^[0-9]+$ ]]; then
            currentlocation=`banshee --query-position`
            currloc=${currentlocation#* }
            roundloc=$( printf "%0.f\n" $currloc )
            seekchange=$(( roundloc - roundseek ))
               if [ "$pbstat" = "playing" ]; then
                  printme="I am rewinding playback by $roundseek seconds."
                  banshact="banshee --set-position=$seekchange"
                  banshow=""
               elif [ "$pbstat" = "paused" ]; then
                  printme="Banshee is paused but I have rewound the pause location by $roundseek seconds."
                  banshact="banshee --set-position=$seekchange"
                  banshow=""
               elif [ "$pbstat" = "idle" ]; then
                  printme="Banshee is neither playing nor paused and thus I am not able to rewind anything"
                  banshact=""
                  banshow=""
               else
                  printme="Is Banshee even on?  I'm confused."
                  banshact=""
                  banshow=""
               fi
         else
            printme="When using rr you must enter whole-numbered seconds in the format \"Bs rr 5\" in order for me to process your request."
            banshact=""
            banshow=""
         fi



##

Have fun with this one too. Suggestions are welcome.

Share

Using find and rm in Conjunction

I downloaded a bunch of music or pictures or somesuch beyond memory, and in each folder the generous sharer included two or three dozen rich text files (.rtf) with some advertising for something about which I couldn’t have cared less.  In the end this accounted for a few hundred unwanted files all ending in .rtf.

In order to find them I used this command:

find /path/to/containing/folder/ -type f -name \*.[Rr][Tt][Ff]

That’s all well and good, but I want to delete them (using rm presumably).

I tried a few guesses…

rm `find /path/to/containing/folder/ -type f -name \*.[Rr][Tt][Ff]`
rm: missing operand

rm "`find /path/to/containing/folder/ -type f -name \*.[Rr][Tt][Ff]`"
rm: cannot remove `': No such file or directory

rm '`find /path/to/containing/folder/ -type f -name \*.[Rr][Tt][Ff]`'

This last one was close but it divided words on spaces and so failed. At this point I thought about changing the internal field separator ($IFS) to the line break (\n) instead of the default space ( ) but instead decided to look for something simpler.

I found it here. In the end my code looked like this:

find /path/to/containing/folder/ -type f -name \*.[Rr][Tt][Ff] -exec rm -f {} \;

You can read quite a bit about the -exec argument in the find manpage (in your terminal type man find).

Warning: This will delete all rich text files located under the containing folder. I was safe to do this because there is nothing on my server stored in the rich text format. If you have files of the same type you are seeking to remove, you will want to take appropriate precautions.

Hope this helps you out.

Share

Malleable Renaming Script

I often get a pile of files where the original maker of those files used some naming scheme which, like spoiled milk, offends my nostrils.  You may call me anal if you’d like, but it prompted me to write this useful script so consider that before you start pointing fingers and laughing.

Essentially this script is set up so that I can easily open it in a text editor, make any changes, save it, and then run it on some folder.  Here it is and we’ll talk a bit below about how to use it.

(Use the pointy brackets button to copy and paste.)

##
#!/bin/bash
# Substitute one string for another within a file name
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

#for filename in *
for filename in */*.[Ff][Ll][Aa][Cc]
#for filename in */*.[Mm][Pp]3
#for filename in */*.[Oo][Gg][Gg]
   do
   mv "$filename" "${filename/ - / – }"
   echo $filename changed
done

exit

 - – 

##

(Please note that your browser will likely not make a distinction between the hyphen (-) and the dash (–) in this post. However, if you click on the double-pointy-bracket button in the code box above you will be able to copy the correct code.)

Ok, so what we have here, after my usual header section, is four possible for loops. Three of them are commented out (since you’ll only be using one at a time), but they are pretty strait-forward.

The first one seeks anything in the current directory (know where you are!) and uses that as input. I use this first one for changing folder names, but be careful because it will change all folders and files that match the pattern which follows. The next three for loops are for three different kind of audio file (FLAC, OGG, and MP3). Since the capital and lower-case letters are in square brackets this means the shell is offered a choice. This is one method for eliminating case-sensitivity (there are others).

After the for loop begins and files/folders are sought out which match the patter under in we have to do something. In this case the script does two somethings: mv and echo (which mean “move” and “display in the terminal” basically).

The variable (here called filename) is then used to stimulate a substitution. Let’s dissect the substitution briefly. In the case shown above the shell will attempt to replace space-hyphen-space with space-dash-space in any flac file it finds in any folder below the current directory.

Why?

First */*.[Ff][Ll][Aa][Cc] tells us, in more human terms, look into any folder (*/) below the current directory for anything (*) that ends in .flac (.[Ff][Ll][Aa][Cc]). When you find one (do) replace the first (/) space-hyphen-space () with (/) space-dash-space () and that’s it (/).

If instead you use “${filename// – / – }” this means when you find one (do) replace all occurrences (//) of space-hyphen-space () with (/) space-dash-space () and that’s it (/).

There is no need to echo the work, but I like to see what it’s doing (or what it’s failed to do). It’s useful but not required. Also the exit command is not, strictly speaking, required; but I use it in this script especially so that I can store characters below it and thereby keep them around for use in the script ( – – ).

Let’s look at an example of this script in use.

Suppose you downloaded (legally, you sneaky bastards) a copy of Jay Farrar and Benjamin Gibbard’s One Fast Move or I’m Gone (Music from Kerouac’s Big Sur). Further suppose that the yahoo who made the flac files insisted on including the artist names on every track. Silly because you keep your collection organized and the album is in a folder with the album name which is in a folder with the artist name. So you’d like to remove the artist names from all the tracks. You could do that by hand for each track, but this is a perfect opportunity for a script so…

The tracks are currently Jay Farrar & Benjamin Gibbard 01 California Zephyr.flac and so your script would look like this:

(Use the pointy brackets button to copy and paste.)

##
#!/bin/bash
# Substitute one string for another within a file name
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

#for filename in *
for filename in */*.[Ff][Ll][Aa][Cc]
#for filename in */*.[Mm][Pp]3
#for filename in */*.[Oo][Gg][Gg]
   do
   mv "$filename" "${filename/Jay Farrar & Benjamin Gibbard /}"
   echo $filename changed
done

exit

 - – 

##

Note two things. First there is a space after Gibbard. Fail to include that and all your tracks begin with a space. Second there is nothing between the slash and the curly-bracket. This means “replace Jay-space-Farrar-space-ampersand-space-Benjamin-space-Gibbard-space” with nothing (or delete it, eh?). This will leave your tracks looking more like 01 California Zephyr.flac .

If you are like me, you like having a dash between the track number and the track name. Others might prefer to use a dot. So for inserting a dash or a dot in the first occurrence of a space that line would look like this:

mv “$filename” “${filename/ / – }”

mv “$filename” “${filename/ /. }”

To run the script open a terminal and move (cd) into the artist folder keeping in mind it would change any other album folder contents—but I know these guys only put out one ablum in this configuration. If you are worried about changing other albums move into the album folder and remove the */ before the *.extension and the script will act on the current directory. When you are in the directory you want, run the script by name (I call mine RemoveModifiable.sh). Be sure the script has execute permissions.

Do be careful with this script. It can be really powerful and can leave you staring at a folder full of files called 01.flac pretty quickly (or several folders if you use the */ construction I am above). As always, work from copies. After all scripts are supposed to make your life easier.

Share

Recursive Prepending Script

As you may recall I prefer to prepend my track names with the album number in the case of multiple disc albums (box sets, double-albums, &c).  I have some rather large (30 CD) sets and am lazy didn’t want to run my other script 30 times, so I wrote this new version to deal with matters recursively.

(There is now an improved version of this script available here. The new version allows the user to select file extensions while the version on the page below is specifically written for FLAC files.)

(Use the pointy brackets button to copy and paste.)

##
#!/bin/bash
# Prepend album number before track numbers automatically and recursively
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

## gather information

# obtain directory in which to work

echo
echo
echo "This script changes FLAC file names under a containing folder which you supply."
echo "It assumes that your discs are separated into numbered sub-folders."
echo "As such sub-folder names should be 01, 02, and so forth."
echo
echo "Example:  \"01/01 – Track Name.flac\" becomes \"01.01 – Track Name.flac\""
echo

read -e -p "I will require the path to the containing folder: "
   if [ -d "$REPLY" ]; then
      printf "\n\nI have confirmed this is a directory.\n"
      directory="$REPLY"
   else
      printf "\n\nI cannot find this directory.\n\n"
      echo "$REPLY"
      exit 1
   fi

read -p "Press <ENTER> to continue (ctrl-c will abort)."
   echo

## do the work

# find all two-digit numerically-named sub-directories under root directory

find "$directory" -type d -name [0-9][0-9] > /tmp/whyme

echo

# parse disc folders into array

declare -a discfind
   let i=0
   while read discline; do
      discfind[$i]=$discline

      # extract disc number from folder path
      disc=$( basename "$discline" )
      discpath=$( dirname "$discline" ) 

      # change file names in sub folder

      for filepath in "/$discpath/$disc/"*.[Ff][Ll][Aa][Cc]; do
         filename=$( basename "$filepath" )
         mv "$discpath/$disc/$filename" "$discpath/$disc.$filename"
         echo $filename changed
      done

      ((i++))
   done < /tmp/whyme

rm /tmp/whyme

exit

##

There are a couple of assumptions that this script makes (which are consistent with my preferences).

For instance, I like my file names to run album number dot track number (01.01 – Track Name.flac). As such I name the containing folders each a two-digit number based on the disc number (disc number three goes into a folder called 03). The script then uses that folder name or disc number (since they are now the same) as the prepend for the file name.

I hope that helps to make your life a little better.

Thanks for stopping.

Share

Script for Skipping Tracks

I use Rhythmbox to play music on my hi-fi system.  There is a command line element for Rhythmbox called rhythmbox-client.  This element can be used to initiate a series of commands in Rhythmbox.  The problem is that these commands end up being a bit longish if you are attached to that machine via ssh.  Here is an example:

DISPLAY=:0.0 rhythmbox-client –play-pause

That’s a lot of typing especially when you are attached via ssh from your Android phone.  It was just too tedious to type all that out on that on-screen keyboard.  So I wrote a script that would manage the arguments I felt were most important.

##
#!/bin/bash
# by JamesIsIn from JamesIsIn.com
# Do something nice today.


   if [ "$1" = "n" ]; then
      argument="--next --print-playing"
      printf "\nI am skipping to the next track:\n\n"
   elif [ "$1" = "p" ]; then
      argument="--play-pause"
      printf "\nI am toggling between play and pause.\n\n"
   elif [ "$1" = "s" ]; then
      argument="--print-playing"
      printf "\nThis is what is currently playing:\n\n"
#   if [ "$1" = "q" ]; then
#      $argument="--enqueue"
   else [ argument = "" ]; 
      printf "\nI'm sorry.  I only understand the following commands:\n\n"
      printf "p (play/pause)\n\n"
      printf "s (show what's playing)\n\n"
      printf "n (skip to next track)\n\n"
      printf "Please try again.\n\n"
      exit
      # I could add --enqueue but I have to figure out to make it work
   fi


# assuming DISPLAY does not matter for local runs (though this is for a single monitor configuration)

DISPLAY=:0.0 rhythmbox-client $argument
echo


exit

##

The use of DISPLAY is required so that rhythmbox-client knows on which monitor Rhythmbox is present.  The configuration above is for a single monitor arrangement.  You’re on your own to figure out what you need in there if you have a different monitor configuration.

So, using my script, if you wanted to issue the same command I mentioned above you would merely type:

Rb p

(Now I just need to streamline the ssh command and this whole procedure will be much easier and thereby more impressive.)

I placed this script in /usr/local/bin on the hi-fi machine. I called it merely Rb. That way the script is available to all users on that machine. Be sure to make the file executable:

sudo chmod a+x /usr/local/bin/Rb

(Since I use my server as a proper server now I moved my script to the share: /media/[share]/Rb so that it would be available across the network.)

Have fun with that.

Share

Conquer Giant APE and Giant FLAC

I have already written posts on splitting and converting from either album-length APE files or album-length FLAC files into track-length FLAC files.

(My post on converting APE files; my post on splitting APE files; my post on splitting FLAC files. You will want to go over these other articles if you have not done any of this before as there are some dependencies which you will not likely have installed.)

That was rather satisfying, but I then found that I would locate entire discographies where every album was a single APE or FLAC.  I did not enjoy the prospect of running each command on each pair of files.

That’s why scripting languages exist.  It’s a good thing I bought that BASH book.

Anyway, this script is designed to recursively locate APE, FLAC, and CUE files under a user-supplied root directory; then it runs shntool and cuetag on each APE/FLAC and CUE pair.  (You can read my previous posts linked above for more details about how these two commands work.)

It even cleans up after itself.  Since it deletes not only the temporary files it creates but also all the APE, FLAC, and CUE files it uses, you will want to pay close attention when the script tells you it is about to clean up all those files.  If you tell it to proceed (by hitting ENTER) without checking its work all those files will be deleted.  As such you should work from copies and you should check that it did what you wanted it to do before hitting ENTER when it asks about cleaning up all files.

Feel free to suggest any improvements.  I hope you find that it works well and improves your lives.

One important thing to note is that it is not an intelligent script.  It cannot determine for you whether a given APE or FLAC file is actually album length.  It assumes that you are offering it a folder (with or without sub-folders) which contains only album-length APE or album-length FLAC files with an accompanying CUE file (one matching pair per containing folder).

Sometimes a cue file will be formatted incorrectly or have some other issue.  Sometimes they can be fixed by opening them in a text editor.  Probably they can always be fixed in a text editor, but it might be difficult to determine what’s wrong with the CUE file.  If a CUE files has issues you will see errors on the command line (if you are staring at your monitor) and the album-length file will not be split/tagged.  You’ll have to work on that pair separately, but since you are working from copies it won’t matter if they get deleted.

If you throw something unusual at it, it’s hard to imagine how it might behave.  It’s likely not dangerous but you should always work from copies and preserve the originals until you are satisfied.

##
#!/bin/bash
# covert APE/FLAC albums to FLAC tracks recursively
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

## gather information

# obtain directory in which to work

printf "\nHello.\n\n"
read -e -p "Please provide the root folder under which I will work recursively: "
if [ -d "$REPLY" ]; then
printf "\nI have confirmed this is a directory.\n\n"
directory="$REPLY"
else
printf "\nI cannot confirm this is a directory.\n\n"
echo "$REPLY"
exit 1
fi

# number, performer, and track names

printf "There are two formatting options for file naming (assuming the cue contains correct information):\n\n"
printf "01 – Track Name.flac\n"
printf "01 – Performer Name – Track Name.flac.\n\n"
read -p "Do you want to include the performer name in the file names (this is only recommended for multiple-artist albums)? [Y or N]: "
if [ "$REPLY" = "Y" ]; then
printf "\nOk, I'll include performer names: 01 – Performer Name – Track Name.flac\n\n"
trackname="%n – %p – %t"
elif [ "$REPLY" = "y" ]; then
printf "\nOk, I'll include performer names: 01 – Performer Name – Track Name.flac\n\n"
trackname="%n – %p – %t"
else [ "$REPLY" = "[Nn]" ];
printf "\nOk, I won't include performer names: 01 – Track Name.flac\n\n"
trackname="%n – %t"
fi

printf "\n\nLet's get to work.\n\n\n"

## do the work

# parse files into respective arrays recursively

find "$directory" -type f -name \*.[Aa][Pp][Ee] -o -name \*.[Ff][Ll][Aa][Cc] > /tmp/whyme

declare -a albumfind
let i=0
while read albumline; do
albumfind[$i]=$albumline
((i++))
done < /tmp/whyme

find "$directory" -type f -name \*.[Cc][Uu][Ee] > /tmp/whyme2

declare -a cuefind
let i=0
while read cueline; do
cuefind[$i]=$cueline
((i++))
done < /tmp/whyme2

# follow array counts

printf "\n\nCounts are as follows. There are "${#albumfind[@]}" albums and "${#cuefind[@]}" cues. Your albums ought to equal your cues. If these counts are not as expected, please inspect the contents of the folder you provided before proceding or re-run the script and select a different folder.\n\n"
read -p "Press <ENTER> to coninue. "

# split apes and flacs; hide album files

for (( i=0 ; i < ${#albumfind[@]} ; i++ )) ; do
# path is cue iteration less file name
albumfolder="${cuefind[i]%/*.*}"
shnsplit -d "$albumfolder" -o flac -f "${cuefind[i]}" -t "$trackname" "${albumfind[i]}" ;
mv "${albumfind[i]}" "${albumfind[i]}".hideme ;
done

# remove 00 files

find "$directory" -type f -name 00\*.[Aa][Pp][Ee] -o -name 00\*.[Ff][Ll][Aa][Cc] > /tmp/whyme3

declare -a deadfiles
let i=0
while read deadline; do
deadfiles[$i]=$deadline
((i++))
done < /tmp/whyme3

for (( i=0 ; i < ${#deadfiles[@]} ; i++ )) ; do
mv "${deadfiles[i]}" "${deadfiles[i]}".hideme ;
done

# tag files

for (( i=0 ; i < ${#cuefind[@]} ; i++ )) ; do
# path is cue iteration less file name
albumfolder="${cuefind[i]%/*.*}"
cuetag "${cuefind[i]}" "$albumfolder"\/*.flac
mv "${cuefind[i]}" "${cuefind[i]}".hideme ;
done

## clean-up

printf "\n\nI have completed all operations to the best of my ability.\nI will now clean up all files.\n\nPlease ensure you are satisfied with the state of things before you continue."
printf "\n\nPlease note: I am about to delete all the album-length files I believe I have used."
read -p "Press <ENTER> to coninue (Ctrl-c will abort clean-up). "

# remove temp files, hideme files, and unset all variables

find "$directory" -type f -name *.hideme > /tmp/whyme4

declare -a hidemes
let i=0
while read hidelines; do
hidemes[$i]=$hidelines
((i++))
done < /tmp/whyme4

for (( i=0 ; i < ${#hidemes[@]} ; i++ )) ; do
rm "${hidemes[i]}" ;
done

rm /tmp/whyme*
unset

exit

##

Have fun with that.

Share

BASH Examples

I have been earnestly studying BASH for a while now, but I recently came across something that will be very useful and I thought I’d pass it along.

Famously enough I run Ubuntu (a Debian-based distribution of Linux).  This is where I do my scripting in BASH, though I also use BASH on my Mac (native) and Windows (through Cygwin).

I have discovered a package called bash-doc which contains a plethora of BASH example scripts and code chunks.

To install bash-doc you just mark it for installation in Synaptic and Apply that change.

  • System —> Administration —> Synaptic Package Manager
  • Scroll down to locate bash-doc
  • Right-click on bash-doc and select Mark for Installation
  • Click the Apply button
  • Double-check the package you are installing and click OK

Alternatively you could enter this simple line of code directly into BASH via the terminal: sudo apt-get install bash-doc (your password will be required either way).

Once you have installed this package you will find a host of examples:

/usr/share/doc/bash/examples/

Have fun with that.  I know I will.

Share