Wednesday, December 24, 2008

Rename list of tv episodes

This is a great ruby script for renaming TV episodes. It's based on the Wikipedia tv episode listing format, which looks like this:

== Season 1: 2003 ==
{| class="wikitable" style="width:98%;"
|-
! width="20"| # !! width="250"|Title !! Patient Portrayer(s) !! Surgery Music !! width="120" | Original airdate !! width="80" | Production code

{{Episode list
|LineColor=AA0004
|Title=Pilot
|OriginalAirDate=July 22, 2003
|Aux1=[[Geoffrey Rivas]]
|Aux2=[[Paint It Black]]—[[The Rolling Stones]]
|ProdCode=101
|EpisodeNumber=1
|ShortSummary=[[Christian Troy|Christian]]'s unethical acceptance of $300,000 from a mobster desperate for a new face put his partnership with [[Sean McNamara|Sean]] in jeopardy and their lives in peril.
|LineColor=AA0004
}}

This happens to be the listing for the first episode of the first season of Nip/Tuck. Anyway, all you need to do to rename all your episodes from a format like "Show Name s(Season Number)e(Episode Number).avi" to "Season (Number) Episode (Number) - Episode Title.avi" is run this script. First you need to look up the Wikipedia episode listing, then click the "Edit" button at the top of the table. Then save all the text in the editor to a file called "episodes.txt" in the same directory as the video files.

#!/usr/bin/ruby



title = ''

ep    = ''

eps   = Hash.new



File.open("episodes.txt").each do |l|

    if l.match(/Title=/)

        title = l.slice(l.index('=')+1, l.length).chop

    end

    if l.match(/ProdCode/)

        ep    = l.slice(l.index('=')+1, l.length).chop

        eps[ep] = title

    end

end



Dir.open('.').each do |f|

    next unless f.match(/\.avi$/)

    if f.match(/s(\d+)e(\d+)/i)

        key = $1.to_i.to_s + $2

        newf = "Season 5 Episode #{$2} - #{eps[key]}.avi"

        File.rename(f, newf)

    else

        puts "No match: #{f}"

    end

end

Yes, it's short, but I've found myself rewriting it countless times in several languages. It is highly useful.

Saturday, November 8, 2008

Building xlslib on MSYS

I was looking for a way to create Excel spreadsheets without needing to resort to Perl's fantastic Spreadsheet::WriteExcel. I wanted something fast and light, so I went out looking and found the library xlslib. This appeared perfect, unfortunately no win32 DLL was available. This is the process to create one.

Follow the installation process for MSYS and MinGW. Install all recommended updates, it will not work otherwise. Install the latest version of libtool(Otherwise the make will fail since it can't find C:/msys/1.0/local/lib/libxls.0.dylib. This is because the old libtool does not know about dll as an alternative extension for dynamic libraries (.dylib is the default for MacOS X.))

Download xlslib and untar it to your MSYS home directory (usually in C:/msys/1.0/home/yourusername. Double click the MSYS icon to open a new session and do the following.

1. Add the following line to the top of the file in ~/xlslib/src/oledoc/oledoc.cpp.
#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
2. Run the following commands.

~$ cd ~/xlslib
~/xlslib$ autoheader
~/xlslib$ touch AUTHORS ChangeLog stamp-h
~/xlslib$ aclocal
~/xlslib$ autoconf
~/xlslib$ touch config/ltmain.sh
~/xlslib$ automake
~/xlslib$ rm config/ltmain.sh
~/xlslib$ ./configure
~/xlslib$ make

3. The make will fail while building targets/test/mainC.c.

4. It will also fail to create a dll file. This can be remedied by running the following commands.

5. First modify libtool in ~/xlslib and change the line that says:
*cygwin*|*msys*) output=`$echo $output | ${SED} -e 's,.exe$,,;s,$,.exe,'` ;;

to
*cygwin*|*msys*) output=`$echo $output` ;;


6. This keeps libtool from adding .exe to the end of the .dll file. Then run the following commands.

~/xlslib$ cd src
~/xlslib/src$ /bin/sh ../libtool --tag=CXX --mode=link g++ -g -O2 -shared -o xlslib.dll -rpath /usr/local/lib overnew.lo blank.lo cbridge.lo cell.lo colinfo.lo continue.lo crc.lo datast.lo docsumminfo.lo extformat.lo font.lo format.lo globalrec.lo index.lo label.lo merged.lo number.lo range.lo recdef.lo record.lo row.lo sheetrec.lo summinfo.lo unit.lo workbook.lo binfile.lo oledoc.lo olefs.lo oleprop.lo
~/xlslib/src$ strip -g xlslib.dll # (Optional) Strip debugging symbols.

5. You now have a working xlslib.dll!

Friday, August 1, 2008

GMail for Symbian s60 Is Crippled

The GMail application for s60 seems to be crippled. Actually, the m.gmail.com website is affected too. How, you ask? Well, have you tried downloading a zip file to your mobile from the GMail application? No? Well, if you do, all you get is a plain text listing of the zip file contents. There does not appear to be any way to download the actual zip file. Worse, if you own a phone with a decent web browser and go to m.gmail.com and try - all you get is a notification "1 attachment." Lovely.

The solution was to turn on IMAP and then setup IMAP in the Nokia Messaging application. This finally allowed me to download the stinking zip file. Hurrah. I'm just glad I have a phone that has this feature.

The attachment I was trying to download was the highly sweet game Lament Island. I bought it on a friend's PC and didn't want to install the Nokia phone manager software (not to mention that I didn't have my data cable.) This isn't all that unusual. What if I had a colleague send me a PowerPoint for a presentation I was giving? I would be totally SOL, if it wasn't for IMAP.

Sunday, July 13, 2008

Flickr Photostream Ordered by Date Taken

After thinking about getting an iPhone since the day they came out I went out and bought a Nokia N95. It was a hard call, I think the main reason was how Apple locks down the iPhone so you can only do cool stuff with it if you bother to jailbreak it. It's just not worth it. Not to mention the sweet 5MP camera on the N95.

Well anyway, I found Showzu. Which lets me instantly post photos I take with the camera onto Flickr and Facebook. This really does solve a major problem with all digital cameras - upload. It's such a pain to copy the files off the camera, index and sort them, color correct them and then post them to someplace where they can be viewed easily. Part of this is the fact that the photos off the camera have such useless names (like dscf08329.jpg.) This means you have to open the photo you want to post, find the filename and then use the browser's find file dialog to upload each file (or at least of zip file you put together.) Not at all elegant. So Showzu is the perfect solution. Or at least it would seem.

Unfortunately the edition options in the phone are not the best. This would not be a problem, since Flickr has a rather nice built in editor. However, the edited photo becomes a new photo in the photo stream. How gay is that? So if I go back to color correct a few photos from a week ago, they will appear like I posted them today. Not cool.

Enter the Flickr::API to the rescue (it would seem.)

















































































































































































































































































































































































































































































#!/usr/bin/perl
##############################################################################################
## flick_dates.pl: Flickr API script to change the date taken to the date posted. ##
## Author : Jonathan Jeffus (jjeffus at gmail dot com) ##
## Copyright (C) 2007 Psylus Development, LLC. ##
##############################################################################################
## This software is released under the same terms as Perl itself (Artistic License) ##
##############################################################################################
use Carp;
use Flickr::API;
use Time::Local qw/timelocal/;
# On linux, comment this out and see open_in_browser()
use Win32::API;
##
## User Servicable Variables
##
my $api_key = '';
my $secret = '';
my $photos_to_update = 500;
##
## Get Write Permission
##
my ($api, $res);
# Connect to the Flickr api and get a frob for authentication.
$api = new Flickr::API({
'key' => $api_key,
'secret' => $secret
});
$res = $api->execute_method('flickr.auth.getFrob');
if (not defined $res) {
croak "Error: Did not get response object.\n";
} elsif ($res->{'success'} != 1) {
croak "Error $$res{'error_code'}: $$res{'error_message'}\n";
}
my $frob = $res->{'tree'}->{'children'}->[1]->{'children'}->[0]->{'content'};
my $url = $api->request_auth_url('write', $frob);
# Open the url in a web browser.
&open_in_browser($url);
# Wait for the user to click "Authorize" in the browser window.
print "A window will open requesting authorization. Hit enter after you grant the application access...\n";
my $line = <>;
# Now get a token.
$res = $api->execute_method('flickr.auth.getToken',
{'frob' => $frob,
'perms' => 'write'});
if (not defined $res) {
croak "Error: Did not get response object.\n";
} elsif ($res->{'success'} != 1) {
croak "Error $$res{'error_code'}: $$res{'error_message'}\n";
}
my $token = $res->{'tree'}->{'children'}->[1]->{'children'}->[1]->{'children'}->[0]->{'content'};
# This could be saved after the first time the application is authorized. This would prevent us from needing to do the first two steps.
print "Token: $token\n";
# Now search for the last $photos_to_update photos by the user.
$res = $api->execute_method('flickr.photos.search',
{'auth_token' => $token,
'user_id' => 'me',
'per_page' => $photos_to_update});
if (not defined $res) {
croak "Error: Did not get response object.\n";
} elsif ($res->{'success'} != 1) {
croak "Error $$res{'error_code'}: $$res{'error_message'}\n";
}
# Go through the response and get all the photo ids.
my $photos = $res->{'tree'}->{'children'}->[1]->{'children'};
my @photos;
foreach my $item (@$photos) {
if ($item->{'name'} eq 'photo') {
push @photos, $item->{'attributes'}->{'id'};
}
}
# Now go through all the photo ids and update them.
foreach my $photo (@photos) {
print "photo id : $photo\n";
# First getInfo on the photo.
$res = $api->execute_method('flickr.photos.getInfo',
{'auth_token' => $token,
'photo_id' => $photo});
if (not defined $res) {
croak "Error: Did not get response object.\n";
} elsif ($res->{'success'} != 1) {
croak "Error $$res{'error_code'}: $$res{'error_message'}\n";
}
my $info = $res->{'tree'}->{'children'}->[1]->{'children'};
# Get date_posted and date_taken.
my $dates;
foreach my $item (@$info) {
if ($item->{'name'} eq 'dates') {
$dates = $item->{'attributes'};
}
}
if (not defined $dates) {
croak "Error: No dates found in object.\n";
}
print "posted unix: ", $dates->{'posted'}, "\n";
print "posted 8601: ", &unixtime_to_iso8601($dates->{'posted'}), "\n";
print "taken old: ", $dates->{'taken'}, "\n";
# Parse the taken date (currently in iso8601 MySQL format.)
my ($taken);
if ($dates->{'taken'} =~ m/(\d\d\d\d)-(\d\d)-(\d\d).(\d\d):(\d\d):(\d\d)/) {
# And convert it back to seconds since the epoch.
$taken = timelocal($6, $5, $4, $3, int($2)-1, int($1)-1900);
print "taken unix: ", $taken, "\n";
print "taken 8601: ", &unixtime_to_iso8601($taken), "\n";
} else {
croak "Error: Invalid date: $$dates{'taken'}: Expected ISO 8601.\n";
}
# Now set the date_posted to the date_taken.
$res = $api->execute_method( 'flickr.photos.setDates',
{'auth_token' => $token,
'photo_id' => $photo,
'date_posted' => $taken});
if (not defined $res) {
croak "Error: Did not get response object.\n";
} elsif ($res->{'success'} != 1) {
croak "Error $$res{'error_code'}: $$res{'error_message'}\n";
}
print "\n";
}
sub open_in_browser {
my $url = shift();
my ($shell);
# On Windows
$shell=new Win32::API('shell32','ShellExecuteA',[qw(N P P P P I)],'N');
$shell->Call(0,'open', $url, 0, 0, 1 );
# On Linux (untested)
# system("`which firefox` '$url'");
}
sub unixtime_to_iso8601 {
my $time = shift();
my ($s, $i, $h, $d, $m, $y) = localtime($time);
$m += 1;
$y += 1900;
my $date = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $y, $m, $d, $h, $i, $s);
return $date;
}

Saturday, May 17, 2008

Red5 Easier Setup

Like most Java "innovations", Eclipse is a barrier to getting any real work done. After worked for 18 hours or so on my modified Red5, Eclipse decided to throw an OutOfMemoryError on a system with two gigabytes of memory. After the IDE crashed it refused to startup again. I deleted ~/.eclipse and this did not fix the problem. I deleted /usr/local/eclipse and untarred a fresh copy of Eclipse for Java EE developers. This did not fix the problem (the IDE would freeze with 100% cpu usage on the splash screen.) I solved this problem with a program that's not written in Java:

$ rm -rf /usr/local/eclipse

It turns out you can build Red5 much easier without the brilliant and innovative Ecplise IDE. First install Ant. Then install Poison Ivy. Download the Red5 0.7.0 tarball here. Untar the tarball in your favorite development directory (WARNING: the Red5 developers did not add a top level directory in the tarball, therefore you need to create your directory first.)

$ cd dev/javasucks
~/dev/javasucks$ mkdir red5; cd red5;
~/dev/javasucks/red5$ tar xvfz ~/red5-0.7.0.tar.gz

At this point you would normally be able to build red5 without a hitch by issuing the fantastic command:

~/dev/javasucks/red5$ ant

However, the Red5 developers didn't add an ivysettings.xml file to the tarball, even though it's in SVN. So you need to go here and download it. Move the file to the red5 top level directory and you should be able to build red5.

~/dev/javasucks/red5$ mv ~/ivysettings.xml .
~/dev/javasucks/red5$ ant

You can run the resulting standalone server by doing:

~/dev/javasucks/red5$ cd dist
~/dev/javasucks/red5/dist# ./red5.sh

Friday, May 16, 2008

Red5 With JDBC Database Connections

I was using Carl Sziebert's guide for getting JDBC connection pooling up and running for Red5. Again I'm struck by the vast idiocy of Java programmers. They are sometimes hilariously stupid. The process of getting red5 JDBC hibernation working is a perfect example. Just take a look at Carl's guide (not disparaging Carl personally, he may be an idiot or he may just be misguided.) At the very least he's publishing some method of making this work. Well anyway, contrast his guide with my guide for getting Apache connection pooling working in Perl. Ok, here's the guide!

use Apache::DBI;

Whew, that was tough. I'm glad these Spring people have created this amazing technology that makes connection pooling possible! What an incredibly elegant solution they have created! Five proprietary XML files to edit. I'm astounded at this cutting edge technological innovation which pushes back the oounds of possibility for mankind! All brought to you by the ingenius language Java, which is creating a wonderful new paradigm of simple programming which anyone can understand and maintain. I'm glad those people are out exhaling carbon dioxide, at least they're doing something useful.

Thursday, May 15, 2008

Eclipse, Spring, Red5 on Ubuntu

This is a log of my attempts to set up a Flash streaming server on Ubuntu 7.10 Server.

First I want to mention that I hate Java developers on a very personal level. Their mediocre mental facilities and sheep like tendencies is not an excuse. Java is the cancerous tumor on the ass of the programming world. The Java ideology is blindly idiotic even to the most simple minded among us (or so you would think.) It is rooted in the notion that you can reduce the number of mistakes (bugs) in a piece of software by making the language extremely verbose. The saga of the installation process for these tools is a testament in itself of the fallacy of this position. Notwithstanding the blindly obvious fact that we all know, after all, that verbose language leads to less mistakes. Right? Do you make more mistakes when you are writing a paragraph for a research paper, or a page of dense legal language? Besides this, which one is easier for an author to write? Which one is easier for someone whose second language is English? Enough said.

Unfortunatly, the simple minded fell for Sun's propaganda back in the 90s. Therefore we're stuck with this bastard child. Ironically, Red5 will eventually make Java irrelavent (by opening the market to alternative FMS implementations.) It is, after all, essentially building a technology which was designed to fulfill Java's original intended use. The day that Java is relegated to a few zealous geriatrics (like Lisp is today) is going to be a happy day.

First, get Red5. Get the .deb from Red5 here. Next run the deceptively simple installation command:

sudo dpkg -i red5_0.7.0_all.deb

On a stock Ubuntu system it will fail horribly. It needs sun-java5-jre.

sudo apt-get install sun-java5-jre

Amazingly, this doesn't work without half an hour of Googling why. It fails with an unsatisfied dependency "sun-java5-bin". Which if you try to install you get an unsatisfied dependency of "sun-java5-jre". Which if you try to install you get a helpful unsatisfied dependency of "sun-java5-bin".

The solution is to run:

sudo apt-get install

And accept their nefarious license agreement. But a quick check of the process list will show that there is no Red candy. There are no error messages to speak of, this is a wonderful example of Java in action. If you snoop around you find /etc/init.d/red5. Why not try and start it?

sudo /etc/init.d/red5 start

Well. It says it started. That's odd, there's nothing in the process list. It looks like it's calling a script /usr/lib/red5/red5.sh. Let's try that.

sudo /usr/lib/red5/red5.sh

Aha! A typical 50 line Java exception. I forget exactly what the exception is, don't really care. The solution is to run:

sudo update-alternatives --config java

Then select the sun-java5-jre option. This exception is ocurring because our wonderful Ubuntu overlords don't like the fact that Java is closed source. They'd rather that we use GCJ for politcal reasons. It is because of this they decided to waste my time. Thankfully, Sun finally pulled their head out of their ass and released their pile of crap as Open Source(tm), which should save us headaches when we have to use this crap in the future. Another run of /etc/init.d/red5 start will give us a working Red5. You should be able to navigate to it at:

http://localhost:5080/

Next we need to install Eclipse, at least if you want to do any development. For those of you who are unaware, java is such a poorly designed language that it requires a mammoth IDE in order to get any work done in it.

# Don't run this, read on for a fix.
sudo apt-get install eclipse

This doesn't work. Yes, it will install Eclipse. Unfortunately, the Ecplise it installs will not work. There is a user friendly Java exception in the upper right corner. The solution is to open "/etc/eclipse/java_home" and move (or add) the line "/usr/lib/jvm/java-1.5.0-sun" at the top of the file. This is because Eclipse is too dumb to find the right JVM on its own. Additionally, the Eclipse version which is in the Ubuntu repositories is too old, it will fail several hours after tweaking with it trying to make it work with Red5. So instead download the Eclipse IDE for Java EE Developers (version 3.3.2 as of this writing.) Untar the file and move it to /usr/local/eclipse.

tar xvfz eclipse-jee-europa-winter-linux-gtk.tar.gz
sudo mv eclipse /usr/local

Now start the Eclipse IDE like so:
/usr/local/eclipse/eclipse

This will fail trying to write a few files to various places which require root access. The program appears to be ignorant about file permissions. You can start it once with:
sudo /usr/local/eclipse/eclipse

Then close it and start it again and the problem seems to go away. You may need to add or edit /etc/eclipse/java_home or not. Now you need to install Springboard or whatever the fuck it's called. The instructions are here. An important thing to notice here is 'Don't try to install the "Spring IDE Dependencies (only for Eclipse 3.2.x)" from the "Dependency"'. Typical of Java developers, their program is not smart enough to figure out which version it is and handle this for you. You also need to install Subclipse for SVN management. And Poison Ivy if you want to build the Red5 system. I used Cal's excellent installation guide here. Otherwise, you appear to be able to build Red5 applcations without them.

If you need to connect to MySQL you can run:

sudo apt-get install libmysql-java

This installs JDBC and the MySQL Connector correctly.

Tuesday, April 22, 2008

Grab Radio Shows Instantly

Whoohoo! I just found out the radio show I listen to is automatically posted every day on the newsgroup alt.binaries.sounds.radio.misc! This makes the program from my last post much simpler. Instead of capturing and encoding and tagging all I need to do is download the show using nget.

First I downloaded the nget binaries for Windows. I extracted this into "C:\nget" using 7-Zip. Then I setup the nget configuration with this .bat file.


@echo off
rem Create the _nget5 directory.
mkdir "%USERPROFILE%\nget5"
rem Copy a fresh _ngetrc
copy C:\nget\_ngetrc "%USERPROFILE%\nget5"
rem Open notepad to edit the file
notepad "%USERPROFILE%\nget5\_ngetrc"


The _ngetrc configuration file is fairly easy to setup. When notepad opens all you need is to look for the (halias line and change <yourhostalias> to MyHost, <yourhostaddress> to "news.whateveryourhostis.com", remove the "#" from the "user" and "password" lines, then change them to match your user name and password. In my case it was for GigaNews. So I changed it to news.giganews.com and then added my user name and password. Incredibly easy.

I then wrote this PureBasic script to fetch the file, extract it and clean up.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Change These Variables ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

home.s = GetEnvironmentVariable("USERPROFILE")
dir.s = home + "/My Documents/My Music/Show"
tmpdir.s = home + "/Local Settings/Temp"
e7z.s = "C:/Program Files/7-Zip/7z.exe"
nget.s = "C:/nget/nget.exe"
group.s = "alt.binaries.sounds.radio.misc"

;;;;;;;;;;;;;;;;;;;;;
;; Do The Download ;;
;;;;;;;;;;;;;;;;;;;;;

started = Date()
showExp.s = FormatDate("Show.%dd-%mm-%yyyy.", started)
SetEnvironmentVariable("HOME", home)
SetEnvironmentVariable("TMPDIR", tmpdir)

; If it's Saturday or Sunday then skip out.
If DayOfWeek(started) = 0 Or DayOfWeek(started) = 6
PrintN("Show is weekdays only! Exiting..")
End
EndIf

; Grab the show.
If RunProgram(nget, "-g "+Chr(34)+group+Chr(34)+" -r "+Chr(34)+showExp+"*"+Chr(34), dir, #PB_Program_Wait) = 0
PrintN("NGet failed!")
End
EndIf

; Check to see if we got it.
If FileSize(showExp+"part1.rar") <= 0
PrintN("Failed to get show: "+showExp)
End
EndIf

; UnRar the files.
If RunProgram(e7z, "e "+showExp+"part1.rar", dir, #PB_Program_Wait) = 0
PrintN("Could not start 7-Zip for decompression!")
End
EndIf

; Check if that succeeded.
; Note: I could use ProgramExitCode() to see
; if it failed or not. I'm just being lazy.
If FileSize(showExp+"mp3") <= 0
PrintN("Decompression failed!")
End
EndIf

; Now unlink the .rar and .nzb files.
#dir = 0

; Note: ExamineDirectory() supports wildcards, that
; is what the +"*" is. It's almost as cool as
; Perl filename globbing.
If ExamineDirectory(#dir, dir, showExp+"*")
While NextDirectoryEntry(#dir)
; Skip "." and ".."
If DirectoryEntryType(#dir) = #PB_DirectoryEntry_File
name.s = DirectoryEntryName(#dir)
; Skip deleting the mp3.
If Right(LCase(name), 3) = "mp3"
Continue
Else
; But delete everything else.
If DeleteFile(dir+"/"+name) = 0
PrintN("Failed to remove file: "+dir+"/"+name+" : "+GetErrorDescription())
EndIf
EndIf
EndIf
Wend
EndIf


You know what the amazing thing is? When this is compiled I get a 25kB .exe file! That's all that is required to run it. I don't need any kind of interpreter, because there is no interpreter. It's a compiled language baby! This is the stuff magic is made of, the worlds first ultra high level compiled language.

Ruby Programming

Ruby sucks. It's that simple.

I'll admit I'm a long time Perl programmer, I'm biased. I started programming life on a Commodore Amiga 2000 back in 1988. My first "Hello World" was in Lattice C. Followed swiftly by a change to Easy AMOS Basic. After the Amiga died the death of Beyond 2000, I switched to the wild and woolly world of PC programming. It was a bloodbath back then. The Amiga programmers were feeling their wild oats all over the faces of PC programmers. Nibbles was ASCII, a beep speaker made beeps. The Amiga programmers could play music and add sound effects with thousands of colors and countless animated naked guys with swords. Eventually I found Linux in '97 and never looked back. We had a website, I wrote a shopping cart for it in Perl. This language made computers what they always should have been. It's a lever on the heart of the machine. You need the answer to a question? You can fire off a quick one liner and bring those precious bogoMIPS to bear in a explosive shower of scrolling ASCII and mysterious regular expressions.

Well, after years of consistent daily Perl, C and Javascript programming I thought I had found all the tools my box might ever need. So naturally I decided to uninstall Perl from my box and install Ruby. Dim witted people are fascinated with Ruby on Rails. I have never been. I've written (or been involved with the writing of) three different complete (still in production) commercial web application frameworks in two different languages. RoR has a Reality Distortion Field equaled only by Steve Jobs. So it's a nice framework, so what? It's dog slow, written in a language that (still) lacks a decent backing library (I'd coin the acronym RINC, RAA Is Not CPAN) and it really offers nothing that isn't available in Perl, Python, PHP or Java. Yet, this is almost single handedly the main thing that brings people to ruby. It's amazing, with a nice name and a good propaganda line you can sell people anything. But I digress, ruby is actually a fairly cool language (I'll get to that in a minute.)

At the very least it whips Python like a tied up diaper sissy. Why you ask? Python is a toy. If programming languages were vehicles, then Python would be a tricycle. It's what you use before you learn how to ride a bicycle, motorcycle or a car. Python protects you, it tells you what you can and cannot do. Python is a crossing guard at the programming kindergarten. It's easy to use, it saves you from what it thinks are bad programming practices. And it single handedly taught a generation of programmers to be whitespace Nazis. Nevertheless, when you grow up, you will decide that touching other object's private variables can be an exciting, naughty and only slightly guilt laden activity. Perl and Ruby make this secret delight a worshipful experience, Python is a bitch nun about it. I can do whatever I want dammit! No matter what Guido says! Fine! I'll leave! Become a Perl programmer. It's nature's natural progression. Big boys can handle name space management.

This was James Gosling's fatal flaw when he created Java. He had no understanding of human nature. His obsessive compulsive control freakdom has tarnished the programming landscape forever. Have you ever met a Java programmer? They're poor shadows of men, rife with cognitive dissonance. They function much like any religion, in that they claim one thing and then do another. The first great act of a Java programmer is finding out ways to beat the system. You see the guilty publications and "brown bag" software out among the Java programming landscape. It hides under names like "introspection" and "byte code decompilation". But admittedly, Gosling's baby grew up into an irate old maid. Only the deep magicians can brew up enough GHB to get her to put out. But there is always, always, always those who spend hours and hours trying.

Take a look in C++ to see why this philosophy is a miserable failure. How many times have you seen (or used) '#define private public' in commercial code? I've seen it several times. Thankfully c++ has this feature, when your ass in on the line it comes in handy.

But this post is about my experiences in the last few weeks using Ruby as my system administration scripting language. I will say that Ruby is pretty cool. It's like a light infusion of Smalltalk, Perl and BASIC. Ruby is just as pragmatic as Perl about providing useful syntactic sugar. The difference is, Ruby did not have to support legacy code. So it's a very sweet language. I mean, Ruby has most of the syntactic sugar of Perl and it doesn't have confusing dollar variables (for local variables anyway). This single handedly fixed the primary objection dimwitted Python programmers levied against Perl. It opens up a whole new world! Look ma, no dollar sign!

The first general system administration Ruby script I wrote was a command line tool for editing ID3 tags. It used the id3lib and Ruby's excellent optparse command line argument processor. It took me maybe ten minutes to finish with no prior Ruby programming experience. It's as simple as:

include "rubygems"
include "id3lib"

mp3tag = ID3Lib::Tag->new("mymp3.mp3")
mp3tag.artist = "Bob Dylan"
tag.update!

It can hardly be any easier and there does not appear to be a CPAN module that is nearly as elegant. I couldn't find anything for Python either.

The next project was for my company, so I was on the clock. It was a fairly ambitious script called from one of our video conversion daemon processes. It's job was to make an encrypted, basic authorize protected HTTP connection to an XML-RPC perl script on one of our web servers. It would then ask if users have uploaded any new videos to convert. If they have then it would download them via FTP and INSERT a record into a SQLite3 database indicating this to our conversion software. Simple right? With ruby, yes it was. I won't reproduce the script here, but I can assure you that it's less than a few hundred lines of highly readable code. I don't think I could've written it as succinctly in Perl and I know I couldn't write it as clearly.

Which brings me to my final project and unexpected twist.

Yesterday I wanted to do what seemed like a two minute scripting task. Use mplayer to dump an audio stream of a radio talk show, then convert the file to an MP3 and tag it. It would run daily from crond or svchost and do its thing so I wouldn't miss my daily dose of right wing bantering. It had to be cross platform (so I could use it on my Windows laptop as well as my Linux desktop.) It sounds simple right? If I didn't want a single solution then I could just use a few BASH scripts called from the crontab; one to start the capture, another one to kill the first at the end of the show and a third one to do the conversion and tagging. Simple. But with Ruby on my side, I could whip something out easily, right?

Wrong. It turns out that Ruby isn't very good at Windows scripting tasks (it doesn't know how to fork() or kill() properly.) My script started out looking like this:


#!/usr/local/bin/ruby

# gem install win32-process
require 'win32/process' # Comment this out when we're on Linux.
require 'timeout'

mplayer = "C:\\mplayer\\mplayer.exe"
stream = "http://someradiostation.co.uk/stream.asf"
dir =
"C:\\Documents and Settings\\u\\My Documents\\My Music\\Show"
title = Time.now.strftime("Radio Show - %Y-%m-%d")

t = Time.now

pid = fork()
if pid != nil
puts "one: "+ pid.to_s
t = Time.now
while 1
n = Time.now
if (n - t) > 15.0
puts "watcher: Killing: "+pid.to_s
Process.kill "SIGINT", pid
break
else
puts "watcher: Sleeping..."
sleep 5
next
end
end
else
Dir.chdir dir
system mplayer, '-streamdump', stream
end

Doesn't that look beautiful?? It's so simple! It doesn't work, and it's not possible for it to work, but it's beautiful!

Why doesn't it work you ask? Well, kill "SIGINT" was obliterating the mplayer process. It was causing the process to do odd things, sometimes it would die, other times it would appear to be dead but would still show in the process list (and it would happily keep grabbing the stream.) This seems to be irreparable, it's a problem with win32-process.

The solution?? Is it Perl? No (but that would work since fork() and kill() work.) Is it Python? No (but that would work too.) The answer came in a whole different language that you would never suspect as a scripting language; PureBasic. I had bought a copy of it last year and I've used it for small systems programming tasks around the company. The thingg is, it's a truly compiled language. It generates native machine code in the same way a C, C++ or Pascal compiler does. The thing is, the syntax is nearly as flexible as Ruby or Perl. Granted, there isn't as much syntactic sugar. But as you can see, it's makes for a great maintenance/administration language. This is the final script in PureBasic.


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; User Serviceable Variables ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

ffmpeg.s = "C:/mplayer/ffmpeg.exe"
mplayer.s = "C:/mplayer/mplayer.exe"
stream.s = "http://someradiostation.com/stream.asf"
dir.s = "C:/Documents and Settings/u/My Documents/My Music/Show"
title.s = FormatDate("%yyyy-%mm-%dd Show", Date())
author.s = "Show Name"
genre.s = "Speech"
album.s = "Show"
logFile.s = dir+"/grabShow.log"
checkEvery.l = 60
showLength.l = (3 * 60 * 60) + (5 * 60) ; 3 hours plus five minutes of leeway.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; "Private" Program Variables ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

success.l = #False
tries.l = 0
started.l = Date()
when.s = FormatDate("%yyyy-%mm-%ddT%hh-%ii-%ss", started)
dump.s = dir+"/stream.dump"
wma.s = dir+"/stream.wma"
checkEvery = checkEvery * 1000

; Delete yesterday's scratch files.
DeleteFile(dump)
DeleteFile(wma)

Enumeration
#Log
EndEnumeration

; Open the log file.
If OpenFile(#Log, logFile) = 0
MessageRequester("Error: grab.exe", "Failed to open log file: "+Chr(10)+logFile)
End
EndIf

; Position the file pointer to the end of the file.
FileSeek(#Log, Lof(#Log))
WriteStringN(#Log, "Starting process: "+when)

; If it's Sunday or Saturday then exit (since I can't find
; the Weekdays Only option in Scheduled Tasks on Windows.)
If DayOfWeek(started) = 0 Or DayOfWeek(started) = 6
WriteStringN(#Log, "Show is weekdays only! Exiting..")
Goto CleanUp
EndIf

TryAgain:
If tries <= 1 WriteStringN(#Log, "Starting mplayer: "+mplayer+" "+"-dumpstream "+Chr(34)+stream+Chr(34)) pid = RunProgram(mplayer, "-dumpstream "+Chr(34)+stream+Chr(34), dir, #PB_Program_Open) If pid <> 0
Delay(2000) ; Give mplayer time to start up.
; Check if it's still running (i.e. capturing stream.)
If IsProgram(pid) And ProgramRunning(pid)
While 1
now = Date()
; If the show is over.
If now - started > showLength
; Check if mplayer is still running.
If IsProgram(pid)
If ProgramRunning(pid)
; Kill it if it is.
WriteStringN(#Log, "Killing mplayer...")
KillProgram(pid)
CloseProgram(pid)
; And bellow a battle cry of success.
success = #True
EndIf
EndIf
Break
EndIf
; If it's not over then check if it's still grabbing.
If IsProgram(pid) And ProgramRunning(pid)
; Write a note in the log saying we checked on it.
WriteStringN(#Log, "Recorded "+Str(now - started)+FormatDate(" seconds, as of %hh:%ii:%ss...", now))
Else
; Otherwise write a note saying it died unexpectedly.
WriteStringN(#Log, "MPlayer died after "+Str(now - started)+FormatDate(" seconds, as of %hh:%ii:%ss...", now))
; And open a dialog notifying the user so they can fix it.
MessageRequester("Error: grab.exe", "Mplayer died unexpectedly durning stream capture!")
Goto CleanUp
EndIf

; All went well, go to sleep.
Delay(checkEvery)
Wend
Else
; Mplayer died before we even got started.
; Give it another try, for some reason on Windows
; it does this sometimes and the second try
; always works.
WriteStringN(#Log, "mplayer died!")
tries = tries + 1
Goto TryAgain
EndIf
EndIf
EndIf

; Did the conversion succeed?
If success = #True
WriteStringN(#Log, "Renaming '"+dump+"' -> '"+wma+"'")
dest.s = dir+"/"+title+".mp3"
Delay(5000)

; Rename the file to a .wma for aesthetic reasons.
If FileSize(dump) <= 0 WriteStringN(#Log, "Did not get a file!") Goto CleanUp Else If RenameFile(dump, wma) = 0 WriteStringN(#Log, "Move failed: "+GetErrorDescription()) Goto CleanUp EndIf EndIf ; At first I was using MPlayer alone to do the capture ; and the conversion to mp3. Then I would use my id3 script ; (written in Ruby) to add an id3 tag. Installing FFMpeg made ; things easier since it supports ID3 and conversion all in ; one elegant command. If RunProgram(ffmpeg, "-i "+Chr(34)+wma+Chr(34)+" -title "+Chr(34)+title+Chr(34)+" -author "+Chr(34)+author+Chr(34)+" -genre "+Chr(34)+genre+Chr(34)+" -album "+Chr(34)+album+Chr(34)+" -y -v -1 "+Chr(34)+dest+Chr(34), dir, #PB_Program_Wait) = 0 WriteStringN(#Log, "Encoding with ffmpeg failed to start!") Goto CleanUp EndIf If FileSize(dest) <= 0 WriteStringN(#Log, "Ran ffmpeg but it did not produce a file!") Goto CleanUp Else WriteStringN(#Log, "File finished in: "+dest) EndIf EndIf CleanUp: WriteStringN(#Log, "Ending process: "+when) CloseFile(#Log)


How about that huh? Simple, cross platform (Windows,Linux and MacOSX), reasonably easy to read and it only take up 35k of memory when it's run. The only disadvantage is the need to recompile whenever you change something, but honestly, how often do you change crontabs like this? It's mostly write and find out about again two years later when it failed in the middle of the night and filled your Inbox with warning emails (or emails from your boss, if you didn't write a Email Me On Failure system.)

The point is, which is more elegant and easy to understand; item a "Time.now.strftime("%Y-%m-%d Show")" or item B "FormatDate("%yyyy-%mm-%ddT%hh-%ii-%ss", Date())? I think PureBasic is actually a good deal more cleaner than Ruby. They both share the fact that you must typecast variables. If PureBasic added some regular expression syntactic sugar (possibly implemented through PCRE) then you might not need a scripting language like Python, Perl or Ruby at all. PureBasic has the added advantage that you have the option to choose closed source distribution of your application. It's also blazing fast and has cross platform native GUI support (without all the bloat of wxWidgets or even FLTK.) Furthermore, it has the full power of the C API's coated in easy to use goodness. If there isn't a cross platform built in function - it's easy to write one using native APIs. What's not to like?

Well, it doesn't have Objects. That's a fly up the nose of OOP evangelicals. I like OOP, I always use it when it's available don't get me wrong, the thing is that all the really massive systems around today are all procedural. All of them. Windows? Procedural (with OOP grafted on.) Linux kernel, written in C? Isn't that right? OS/360? MySQL? Apache? DB2? Perl? Ruby? All written in procedural languages.

The point is not that Objects aren't nice, it's that procedural programming can accomplish quite a lot. It's not an obstacle, in fact, OOP looks more like LISP when it comes to actual applied use versus academic evangelism.

So over the next week I'm going to play with PureBasic as a scripting language, just for the fun of it. And actually ruby is kind of cool but every tool has it's place. I'll use Ruby when I need a clean, easy to maintain, reasonably complex scripting task done that does not rely on external libraries or POSIX functionality. I'll use Perl when I need to fire off a write-once one liner or a high performance mod_perl website. I'll use PureBasic when I feel like it. And I'll never use Python, for the same reason I'll never know what it's like to give birth.