Monday, June 1, 2009

Adjust volume on directory of mp3s

Need to raise or lower the volume on an entire directory of mp3s? mp3gain is a nice program, doesn't need to re-encode the mp3s. Someone compiled a binary for OSX which I just placed in /usr/bin/ and it works beautifully.
find . -iname '*.mp3' -exec mp3gain -r -m +2 -c {} \;

Friday, May 22, 2009

Rip streamed albums to MP3

So, there are some sites popping up that let you stream entire albums (i.e. not just samples like Amazon). For example, deezer.com and lala.com (first play only is free). That's great, but not very portable. I listen to alot of music on my ipod. So here's a way to use Ambrosia Software's WireTap Studio and a script to rip the stream and save entire albums as separate mp3s. The script uses the MusicBrainz database to look up album and track data for MP3 tagging purposes. The sound quality isn't awesome (96kbps I believe) but its a great way to listen to music to see if you like it or not. Then, of course, you go buy it. Full instructions included in script comments.
#!/usr/bin/perl

# HOW TO SET UP A WIRETAP BROWSER RIP
#
# Open the Wiretap Recording Sessions window.  Click "New" button.  Add the following info to
# the "Saving" tab:
#
# Description: Enter "Browser Rip"
# Select "Name Automatically"
# Save Files To: Library
# Select "Prefix" and enter "0"
# Suffix: Increment
# Split: On Silence (Configure: -64.0db Minimum Silence Duration: 0.7s)
# Un-check "Record Lossless Original"
# Un-check "Open Recording In Editor"
#
# In the "Source" tab:
#
# Source One: Safari (or Firefox)
# Source Two: None
# Record: Trim All Silence (Configure: -64.0db Minimum Silence Duration: 2.0s)
# Check "Mute Mac Audio Sources"
#
# In the "Format" tab:
#
# Presets: MP3-High Quality
#
# Leave the "Effects", "Processing" and "Schedule" tabs at their defaults and click OK.
#
# To record, FIRST click on the "Browser Rip" Line in Recording Sessions and then "Start"
# THEN press "play" on whatever you are playing in the browser.  You will not hear sound,
# because it's muted.  Let the recording play in its entirety, and press "Stop" in
# Recording Sessions when it's done.  If everything went well, one track per each song
# will appear in the Library and should be ready for export.
#
# Note: Often the last track is empty - look at the times of the track and delete it if it's there.
#
# HOW TO SPLIT TRACKS IN WIRETAP SO THAT THEY WILL BE NAMED PROPERLY HERE
#
# When a recorded track in wiretap actually contains two tracks it needs to be manually split.
# Double-click on the track in the Library to load it in the editor.  Find where the track is 
# to be split (by track length or just by looking) and click there to set the cursor at that point.
# Then in the "Editor" menu choose "add marker".  Then in the Editor menu select "Split Audio"
# In the dialog that appears, in the Name field add the original name of the track.  Select
# Save to Library, and Split On "Marker".  Click OK and it will create two new tracks in the Library:
#
# [track name]_split
# [track name]_New Marker [numbered if more than one]
# ...
#
# The one with the "_split" suffix is the first track, and the "_New Marker" is the second (and so forth).
# Re-name them as such:
#
# [track name]a
# [track name]b
# ...
#
# Then delete the original track.
#
# OTHER NOTES
#
# - To use the non-default (wiretap) source folder specify it when calling this script:
#   $ cddb.pl /Users/guest/Desktop/new_folder/
#   This is useful when you want to tag and add a set of mp3s ripped from somewhere else.
# One caveat is they *have* to appear in alphabetical order when you type ls -al
#

use strict;
use WebService::MusicBrainz::Release;
use WebService::MusicBrainz::Artist;
use WebService::MusicBrainz::Track;
use MP3::Tag;
use File::Copy;

# location of raw mp3 files
my $datadir = '/Users/rory/Music/Import/wiretap/';
my $libdir = '/Volumes/numchux/mp3/';

# itunes stuff
my $add_to_itunes = "Yes"; # works only on Mac OS X - set to No for all other OS's
my $itunes_bookmarkable = "No";

# check if we're supposed to use an alternate data dir
my $altdir = 0;
if ( ($ARGV[0]) && (-d $ARGV[0]) ) {
 $altdir = 1;
 $datadir = $ARGV[0];
}

# put the data files in an array sorted by date/time created - accessible by $fullpaths[]
my @filelist = ();
my @fullpaths = ();
my @todelete = ();

# only do the following for the wiretap rips:
if (!$altdir) {

 # rename the files so we can sort by name
 opendir(DIR, $datadir) || die "can't opendir $datadir: $!";
 my @prefiles = readdir(DIR);
 closedir(DIR);
 my $mp3count = 0;
 foreach my $prefile (@prefiles) {
  my $curfile = $datadir . $prefile;
  if ($prefile =~ /\.[Mm][PpOo][3Vv]$/) {
   $mp3count++;
   my $skip = 0;
   my $final_file;
   my $quoted = quotemeta($curfile);
   my $basename = `/usr/bin/basename $quoted`;
   $basename = &stripCrap($basename);
  
   if ($basename =~ /^0\.original\.([Mm][PpOo][3Vv])$/) {
    $final_file = $datadir . '00' . '.original.' . $1;
   } elsif ($basename =~ /^0 (\d\d\d?)\.original\.([Mm][PpOo][3Vv])$/) {
    $final_file = $datadir . $1 . '.original.' . $2;
   } elsif ($basename =~ /^0 (\d\d\d?[a-zA-Z])\.original\.([Mm][PpOo][3Vv])$/) {
    $final_file = $datadir . $1 . '.original.' . $2;
   } elsif ($basename =~ /^0 (\d)\.original\.([Mm][PpOo][3Vv])$/) {
    $final_file = $datadir . '0' . $1 . '.original.' . $2;
   } elsif ($basename =~ /^0 (\d[a-zA-z])\.original\.([Mm][PpOo][3Vv])$/) {
    $final_file = $datadir . '0' . $1 . '.original.' . $2;
   } else {
    $skip = 1;
   }
   if (!$skip) {
    # move file to its new, properly aplh'd name
    move($curfile,$final_file);
   }
   
   # if it's a .mov file we need to convert it
   if ($final_file =~ /^(.*?)\.([Mm][Oo][Vv])$/) {
    my $newfile = $1 . '.mp3';
    my $output = `/sw/bin/ffmpeg -i $final_file -ar 44100 -ab 160k $newfile`;
    if (-e $newfile) {
     unlink($final_file);
     $final_file = $newfile;
     $curfile = $newfile;
    }
   } # end check for .mov
   
   # add files to array so we delete after we;'re done
   push(@todelete,$curfile);
  }
  if ($prefile =~ /\.meta\.plist$/) {
   push(@todelete,$curfile);
  }
 }
 
 # if we don't have an mp3count, die
 if ($mp3count==0) {
  print "no mp3s to convert\n"; exit;
 }

} # end if for !$altdir (is a wiretap rip)

# sort data dir by name
opendir(DH, $datadir) || die "Can't open directory $datadir: $!\n"; 
 @filelist = sort { "$datadir/$b" <=> "$datadir/$a" } grep { -f "$datadir/$_" && /\.[Mm][PpOo][3Vv]$/ } readdir DH;
closedir(DH);

my $i=1;
my $tracks = @filelist;
foreach my $file (@filelist) {
 $fullpaths[$i] = $datadir . $file;
 #print "$fullpaths[$i]\n";
 $i++;
}

# prompt user for artist name
my $artist_name  = &promptUser("Enter Artist Name ","");
if ( !$artist_name ) {
 print "No artist name entered, quitting\n";
 exit;
}

my $at = WebService::MusicBrainz::Artist->new();
my $response = $at->search({ NAME => $artist_name, LIMIT => 15 });
my @artists = $response->artist_list(); # get first in list

# get the most relevant search results for query
my @artist_id_arr = ();
my @artist_name_arr = ();
my $i = 1;
my ($artist_id,$artist_name);
foreach my $artist (@artists) {
 $artist_id_arr[$i] = $artist->{id};
 my $tmpname = $artist->{name};
 $tmpname =~ s/[^[:ascii:]]+//g;
 $artist_name_arr[$i] = $tmpname;
 print "[${i}] $tmpname\n";
 $i++;
}
my $artist_number  = &promptUser("Matching Artist ","1");
if ($artist_id_arr[$artist_number]) {
 $artist_id = $artist_id_arr[$artist_number];
 $artist_name = $artist_name_arr[$artist_number];
} else {
 print "Invalid artist number entered, quitting\n";
 exit;
}

# get the releases for specified artist
my $rl = WebService::MusicBrainz::Release->new();
my $response = $rl->search({ ARTISTID => "$artist_id" });
my @releases = $response->release_list();

my @release_id_arr = ();
my @release_name_arr = ();
my @release_date_arr = ();
my $i = 1;
my ($release_id, $release_name, $release_date);
foreach my $release (@releases) {
 $release_id_arr[$i] = $release->{id};
 $release_name_arr[$i] = $release->{title};
 my $date = "unknown date";
 if ($release->{release_event_list}->{events}[0]->{date}) {
  $release->{release_event_list}->{events}[0]->{date} =~ /^([0-9]{4})/;
  $date = $1;
 }
 $release_date_arr[$i] = $date;
 print "[${i}] $release->{title} ($date, $release->{track_list}->{count} tracks)\n";
 $i++;
}
my $release_number  = &promptUser("Album Name ","1");
if ($release_id_arr[$release_number]) {
 $release_id = $release_id_arr[$release_number];
 $release_name = $release_name_arr[$release_number];
 $release_date = $release_date_arr[$release_number];
} else {
 print "Invalid release number entered, quitting\n";
 exit;
}

# List the tracks
my $release_search = $rl->search({ MBID => "$release_id", INC => 'tracks' });
my $release_search_first = $release_search->release();
my $track_ws = WebService::MusicBrainz::Track->new();
my $track_num = 0;

my @final_tracks = ();
foreach my $track (@{ $release_search_first->track_list()->tracks() }) {
 $track_num++;

 # query track data with artist details
 # artist at the release level could be "Various Artists",
 #   so we need to query each track for it's artist
 my $track_response = $track_ws->search({ MBID => $track->id(), INC => 'artist' });

 my $found_track = $track_response->track();
 print $track_num, ": ", $found_track->title(), " - ", $artist_name,  " - ", $release_name,  " - ", $release_date, "\n";
 
 # write this info to the file
 &id3v2tag($fullpaths[$track_num],$found_track->title(),$release_date,$artist_name,$release_name,"rock","lame");
 
 # rename the mp3 file
 my $curtrack = $track_num;
 if ($curtrack < 10) { $curtrack = '0' . $curtrack; }
 my $curtrackname = replaceSpace(stripNonAlph($found_track->title()));
 my $trackfinal = $datadir . $curtrack . '_' . $curtrackname . '.mp3';
 move($fullpaths[$track_num],$trackfinal) or die "Move failed: $!";
 push(@final_tracks,$trackfinal);
}

# if our libdir is attached, let's move the stuff there.
if (not(-d $libdir)) { print "library not attached, not moving files.  But they are done.\n"; exit; }

my ($artistlib,$albumlib,$tmp1artist,$tmp2artist,$tmp3artist,$tmp1album,$tmp2album,$tmp3album);
# check if the artist dir exists
$tmp1artist = $libdir . $artist_name . '/';
$tmp2artist = $libdir . replaceSpace(stripNonAlph(lc($artist_name))) . '/';
$tmp3artist = $libdir . capWords($artist_name) . '/';

if (-d $tmp1artist) { 
 $artistlib = $tmp1artist; 
} elsif (-d $tmp2artist) {
 $artistlib = $tmp2artist; 
} elsif (-d $tmp3artist) {
 $artistlib = $tmp3artist; 
} else {
 system("mkdir $tmp2artist");
 $artistlib = $tmp2artist;
}
print "ARTIST: $artistlib\n";

# check if the album dir exists
$tmp1album = $artistlib . $artist_name . '/';
$tmp2album = $artistlib . replaceSpace(stripNonAlph(lc($release_name))) . '/';
$tmp3album = $artistlib . capWords($release_name) . '/';

if (-d $tmp1album) { 
 $albumlib = $tmp1album; 
} elsif (-d $tmp2album) {
 $albumlib = $tmp2album; 
} elsif (-d $tmp3album) {
 $albumlib = $tmp3album; 
} else {
 system("mkdir $tmp2album");
 $albumlib = $tmp2album;
}

# move the tracks to the library
print "MOVING TRACKS TO: $albumlib\n";
foreach my $fulltrack (@final_tracks) {
 move($fulltrack,$albumlib) or die "Move failed: $!";
}

# clean up files
foreach my $delete (@todelete) {
 unlink($delete);
}

# add to itunes library
if ($add_to_itunes eq 'Yes') {
 
 # prompt user for what playlist to add (if any)
 my $add_to_itunes_playlists  = &promptUser("Enter playlist you want to add these to [blank for none] ","");
 
 # add all new tracks to an array
 my @additions = ();
 opendir(DH, $albumlib) || die "Can't open directory $albumlib: $!\n"; 
  my @newtrax = sort { "$albumlib/$b" <=> "$albumlib/$a" } grep { -f "$albumlib/$_" && /\.[Mm][Pp][3]$/ } readdir DH;
 closedir(DH);
 
 # loop thru array and add tracks to itunes
 foreach my $newtrack (@newtrax) {
  my $fullnewtrack = $albumlib . $newtrack;
  my $return = &iTunes($fullnewtrack,$add_to_itunes_playlists,$add_to_itunes,$itunes_bookmarkable);
  if ($return) {
   print "track $fullnewtrack added to itunes\n";
  }
 }
} # end if for add to itunes

print "Done\n";
exit;

################### SUBS ###################


# for getting the basename of a file without using a module
sub basename {
 my $file = shift;
 $file =~ s!^(?:.*/)?(.+?)(?:\.[^.]*)?$!$1!;
 return $file;
} # end sub basename

sub stripNonAlph {

 my($text) = shift;
 $text =~ s/([^0-9a-zA-z ])//g;
 return ($text);

} # end sub strip non alph

sub replaceSpace {
 my ($text) = shift;
 $text =~ s/ /_/g;
 return $text;
}

sub capWords {
 my ($text) = shift;
 $text =~ s/\b(\w)/\u$1/g;
 return $text;
}

sub trim {

 my $text = shift;
 $text =~ s/^\s+//;
 $text =~ s/\s+$//;
 return $text;
}

sub stripCrap {

 my $string = shift;
 $string =~ s/\n//g;
 $string =~ s/\r//g;
 $string =~ s/\r\n//g;
 $string =~ s/\012//g;
 $string =~ s/\015//g;
 $string =~ s/\012\015//g;

 return($string);
}

sub id3v2tag {
 # adds id3v2 tag to mp3s

 my ($file,$title,$year,$author,$album,$genre,$encoder) = @_;
 my $mp3 = MP3::Tag->new($file);
 $mp3->get_tags();
 $mp3->new_tag("ID3v2");
 $mp3->{ID3v2}->add_frame("TALB", "$album");
 $mp3->{ID3v2}->add_frame("TIT2", "$title");
 $mp3->{ID3v2}->add_frame("TPE1", "$author");
 $mp3->{ID3v2}->add_frame("TCON", "$genre");
 $mp3->{ID3v2}->add_frame("TSSE", "$encoder");
 $mp3->{ID3v2}->add_frame("TYER", "$year");
 $mp3->{ID3v2}->write_tag;
 $mp3->close();

} # end sub id3v2tag

sub promptUser {

   #-------------------------------------------------------------------#
   #  two possible input arguments - $promptString, and $defaultValue  #
   #  make the input arguments local variables.                        #
   #-------------------------------------------------------------------#

   my ($promptString,$defaultValue) = @_;

   #-------------------------------------------------------------------#
   #  if there is a default value, use the first print statement; if   #
   #  no default is provided, print the second string.                 #
   #-------------------------------------------------------------------#

   if ($defaultValue) {
      print $promptString, "[", $defaultValue, "]: ";
   } else {
      print $promptString, ": ";
   }

   $| = 1;               # force a flush after our print
   $_ = ;         # get the input from STDIN (presumably the keyboard)


   #------------------------------------------------------------------#
   # remove the newline character from the end of the input the user  #
   # gave us.                                                         #
   #------------------------------------------------------------------#

   chomp;

   #-----------------------------------------------------------------#
   #  if we had a $default value, and the user gave us input, then   #
   #  return the input; if we had a default, and they gave us no     #
   #  no input, return the $defaultValue.                            #
   #                                                                 #
   #  if we did not have a default value, then just return whatever  #
   #  the user gave us.  if they just hit the  key,           #
   #  the calling routine will have to deal with that.               #
   #-----------------------------------------------------------------#

   if ("$defaultValue") {
      return $_ ? $_ : $defaultValue;    # return $_ if it has a value
   } else {
      return $_;
   }
} # end sub promptuser

#################################################################################################
###################                ITUNES-SPECIFIC SUBROUTINES           #####################
#################################################################################################

sub iTunes {
 # handles vairous itunes tasks that will only work on a mac os x
  
 my ($file,$add_to_itunes_playlists,$add_to_itunes,$itunes_bookmarkable) = @_;

 # check that we're on Darwin and only do this stuff if we are
 my $darwin = checkForDarwin();
 if ( ($darwin) && ($add_to_itunes eq 'Yes') ) {
 
  # if the files exists, add to itunes library
  my $finder;
  if (-e $file) {
   $finder = addToiTunes($file);
  }
  
  # set the track to bookmarkable
  if (($finder) && ($itunes_bookmarkable eq 'Yes')) {
   my $result = setBookMarkable($finder);
  }
  
  # add it to a itunes playlist(s)
  if ( ($add_to_itunes_playlists) && ($finder) ) {
   
   # split the string into an array on commas (can contain multiple playlists)
   my @playlists = split(/,/,$add_to_itunes_playlists);
   
   # loop thru the playlists...
   foreach my $itunes_playlist (@playlists) {
   
    $itunes_playlist = trim(stripNonAlph($itunes_playlist));
    # check if the pl exists
    my $pl_check = checkPlaylistExists($itunes_playlist);
    if ( (not($pl_check =~/^\d/)) || (not($pl_check >= 1 )) ) {
     # create the playlist
     $pl_check = createPlaylist($itunes_playlist);
    } # end if for playlist does not exist
    
    if (($pl_check =~/^\d/) && ($pl_check >=1 )) {
     # check to make sure playlist(s) exist
     my $return = addToPlaylist($finder,$itunes_playlist);
    } # end if for playlist exists
   
   } # end loop thru itunes playlists
  } # end if for add to playlists
 
 } # end if for is a mac and wants stuff added to lib
 
 return 1;

} # end sub iTunes

sub checkForDarwin {
 # checks that we're running darwin
 
 my $darwin = `uname -s`;
 $darwin = trim(stripNonAlph(lc($darwin)));
 if ($darwin =~ /^darwin/) {
  return 1;
 } else {
  return 0;
 }

} # end sub for check for darwin

sub addToiTunes {
 # adds the file to the itunes library
 
 my $file = shift;
 my $ref = `/usr/bin/osascript -e 'tell application "iTunes" to add POSIX file "$file"'`;
 $ref = &stripNonAlph($ref);
 sleep 2; # sleep so we can allow itunes to process is
 return ($ref);
 
} # end sub addToiTunes

sub setBookMarkable {
 # sets the track so it remembers its last playback position (set the bookmarkable flag to true)
 
 my $finder = shift;
 my $ref = `/usr/bin/osascript -e 'tell application "iTunes" to set bookmarkable of $finder to true'`;
 $ref = &stripNonAlph($ref);
 return ($ref);
 
} # end setBookMarkable

sub addToPlaylist {
 # adds the file to a particular playlist
 
 my ($finder,$playlist) = @_;
 my $ref = `/usr/bin/osascript -e 'tell application "iTunes" to add (get location of $finder) to playlist "$playlist"'`;
 $ref = &stripNonAlph($ref);
 return ($ref);

} # end sub addtoplaylist

sub checkPlaylistExists {
 # checks if the itunes playlist exists
 
 my $playlist = shift;
 $playlist = &stripNonAlph($playlist);
 my $exists=`/usr/bin/osascript << END
 tell application "iTunes"
  set myCount to 0
  repeat with i from 1 to (the count of the playlists)
   set this_playlist to playlist i
   try
    if the name of this_playlist is "$playlist" then
     set myCount to myCount + 1
    end if
   end try
  end repeat
  return myCount
 end tell
 END`;
 
 $exists = &stripNonAlph($exists);
 return ($exists);
} # end check if playlist exist

sub createPlaylist {
 # creates an itunes playlist
 
 my $playlist = shift;
 $playlist = &stripNonAlph($playlist);
 my $newpl=`/usr/bin/osascript << END
 tell application "iTunes"
  set myList to make new playlist
  set name of myList to "$playlist"
  return 1
 end tell
 END`;
 $newpl = &stripNonAlph($newpl);
 return ($newpl);
 
} # end sub createplaylist

sub checkForiTunes {
 # check to see if itunes is installed
 # going to work only on a mac of cours
 
 my $installed=`/usr/bin/osascript << END
 tell application "Finder"
  try
   exists application file id "hook"
   return 1
  on error
   return 0
  end try
 end tell
 END`;
 $installed = &stripNonAlph($installed);
 return ($installed);

} # end sub check for itunes install

Friday, May 15, 2009

Ripping Streams from KEXP

Seattle's KEXP is one of my favorite internet radio stations. They have live streaming in 128kbps with this link:
http://kexp-mp3-128k.cac.washington.edu:8000/listen.pls
Great for ripping shows for yer ipod. But if you miss a show...KEXP's streaming audio archive is in RealMedia and Windows asf formats. They have a complex interface in order to obscure the links, but the format looks like this. They are in 6-hour chunks. You can edit the timestamps and start times to get the show you want:
mms://media-wm.cac.washington.edu/kexpspool/2009041800to04190000_1-1413k.asf
rtsp://media-rm.cac.washington.edu/kexpspool/200904181800to04190000_2-256k.rm?start=3:0:0.0

Rory's Audio Sample Library

I love sampling funny or interesting audio bits that I find. I am always adding new ones. They are available here.

Wednesday, May 13, 2009

Quick Add Currently Playing Track to iTunes Playlist

Applescript to add the currently playing iTunes track to a playlist. It will create the playlist if it doesn't exist. Will not add the track if it's already on the designated playlist. Designed to be executed with a "trigger" event (keystroke shortcut) with Quicksilver or some other shortcut program.
set currentApp to current application
tell currentApp
 activate
 set myList to the text returned of (display dialog "Enter playlist to add track to" default answer "")
end tell

--exit if they didn't enter anyting
if the myList is "" then return

--make sure itunes is installed
property okflag : false
tell application "Finder"
 if (get name of every process) contains "iTunes" then ¬
  set okflag to true
end tell

if okflag then
 set myMessage to ""
 tell application "iTunes"
  set oldfi to fixed indexing
  set fixed indexing to true
  set thisTrack to (get location of current track)
  set dbid to (get database ID of current track)
  
  --see if the playlist exists
  if exists user playlist myList then
   --do nothing for now
  else
   make new user playlist with properties {name:myList}
  end if
  set currentList to playlist myList
  
  --see if the track exists on the playlist
  set currentIDs to {}
  try
   if exists (track 1 of currentList) then -- if there are some tracks - at least one -- get their ids
    copy (get database ID of every track of currentList) to currentIDs -- list
   end if
  on error errText number errnum
   if errText does not contain "Invalid index." then
    error errstr number errnum
   end if
  end try
  
  --add the track to playlist or show error
  if currentIDs does not contain dbid then -- if id not already present add the track
   add thisTrack to currentList
   set myMessage to " Track added to playlist " & myList
  else
   set myMessage to " Selected track already on playlist " & myList
  end if
  set fixed indexing to oldfi
 end tell
end if

--show our output message
-- Check if Growl is running:
tell application "System Events"
 set isRunning to ¬
  (count of ¬
   (every process whose name is "GrowlHelperApp")) > 0
end tell

--Only display growl notifications if Growl is running:
if isRunning = true then
 
 tell application "GrowlHelperApp"
  -- Make a list of all notification types:
  set the allNotificationsList to ¬
   {"Notification 1", "Notification 2"}
  
  -- Make a list of the default enabled notifications:
  set the enabledNotificationsList to ¬
   {"Notification 1"}
  
  -- Register the script with Growl
  -- using either "icon of application"
  -- or "icon of file":
  register as application ¬
   "add_to_playlist" all notifications allNotificationsList ¬
   default notifications enabledNotificationsList ¬
   icon of application "Script Editor"
  
  -- Send a notification:
  notify with name "Notification 1" title "Track add output" description myMessage application name "add_to_playlist"
 end tell
else
 tell currentApp
  activate
  display dialog myMessage giving up after 1
 end tell
end if

--put the focus back on the app we were using before we called this script with quicksilver
--quicksilver became the frontmost app because it's what executed this script
tell application "System Events"
 keystroke tab using (command down)
 set app_name to name of the first process whose frontmost is true
 keystroke tab using (command down)
 --return app_name
end tell


This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]