O'Reilly Hacks
oreilly.comO'Reilly NetworkSafari BookshelfConferences Sign In/My Account | View Cart   
Book List Learning Lab PDFs O'Reilly Gear Newsletters Press Room Jobs  

Buy the book!
Spidering Hacks
By Kevin Hemenway, Tara Calishain
October 2003
More Info

Robot Karaoke
Who says people get to have all the fun? With this hack, you can let your computer do a little singing, by scraping the LyricsFreak.com web site and sending the results to a text-to-speech translator
The Code
[Discuss (1) | Link to this hack]

There are things that are text-only and things that are multimedia. Then there's this hack, which turns boring old text into multimedia—specifically, a .wav file.

This hack, as it stands, is actually pretty silly. It searches the lyric collections at LyricsFreak.com for the keywords you specify, then sends the matching lyrics to yet another site (http://naturalvoices.com) that turns them into a .wav file. If you're running a Win32 system, the code will then automatically play the .wav file ("sing" the lyrics, for some narrow definition of sing) via the Win32::Sound module (http://search.cpan.org/author/ACALPINI/Win32-Sound/).


Listening to your computer's rendition of the Spider-Man theme song can be detrimental to your health.

As you're playing with this code, you might want to think of more sublime and less ridiculous implementations. Do you have a site read by low-vision people? Are there short bits of text, such as a local weather forecast, that would be useful for them to have read aloud? Would it be helpful to have a button that would convert a story summary to a .wav file for later download?

The Code

One of the modules used with this code, Win32::Sound, is for Win32 machines only. Since it's used to play back the generated .wav file, you will not get a "singing" robot if you're on a non-Win32 machine; you'll just get a .wav file, suitable for playing through your preferred music player.

Save this script as robotkaroake.pl:

#!/usr/bin/perl -w
use strict;
use LWP::Simple;
use URI::Escape;
use Win32::Sound;
use SOAP::Lite;

# use your own Google API key here!
my $google_key  = "your Google key here";
my $google_wdsl = "GoogleSearch.wsdl";

# load in our lyrics phrase from the command line.
my $lyrics_phrase = shift or die "Usage: robot-karaoke.pl <phrase>\n";

# and perform the search on Google.
my $google_search_term = "intitle:\"$lyrics_phrase\" site:lyricsfreak.com";
my $googleSearch = SOAP::Lite->service("file:$google_wdsl");
my $result = $googleSearch->doGoogleSearch(
                      $google_key, $google_search_term,
                      0, 10, "false", "", "false",
                      "", "", "");

# if there are no matches, then say so and die.
die "No LyricsFreak matches were found for '$lyrics_phrase'.\n"
          if $result->{estimatedTotalResultsCount} == 0;
# and take the first Google result as
# the most likely location on LyricsFreak.com.
my @results         = @{$result->{'resultElements'}};
my $first_result    = $results[0];
my $lyricsfreak_url = $first_result->{'URL'};
print "Downloading lyrics from:\n $lyricsfreak_url\n";

# and download the data from LyricsFreak.com.
my $content = get($lyricsfreak_url) or die $!;
print "Connection to LyricsFreak was successful.\n";

# we have the data, so let's parse it.
# all lyrics are stored in a pre tag,
# so we delete everything before and after.
$content =~ s/.*<pre><b>.*<\/b><br>//mgis;
$content =~ s/<\/pre>.*//mgis;
my @lyrics_lines = split("\x0d", $content);

# AT&T's demo TTS service takes a maximum of 30 words,
# so we'll create a mini chunk of the lyrics to send off.
# each of these chunks will be sent to the TTS server
# then saved seperately as multiple mini-wav files.
my (@lyrics_chunks, $current_lyrics_chunk); my $line_counter = 0;
for (my $i = 0; $i <= scalar(@lyrics_lines) - 1; ++$i) {
    next if $lyrics_lines[$i] =~ /^\s*$/;
    $current_lyrics_chunk .= $lyrics_lines[$i] . "\n";

    if (($line_counter == 5) || ($i == scalar(@lyrics_lines) - 1) ) {
        push(@lyrics_chunks, $current_lyrics_chunk);
        $current_lyrics_chunk = ''; $line_counter = 0;
    } $line_counter++;

# now, we'll go through each chunk,
# and send it off to our TTS server.
my @temporary_wav_files;
foreach my $lyrics_chunk (@lyrics_chunks) {

    # and download the data.
    my $url = 'http://morrissey.naturalvoices.com/tts/cgi-bin/nph-talk';
    my $req = HTTP::Request->new('POST', $url); # almost there!
    $req->content('txt=' . uri_escape($lyrics_chunk) .
    my $res = LWP::UserAgent->new->simple_request($req);

    # incorrect server response? then die.
    unless ($res->is_success || $res->code == 301) {
       die "Error connecting to TTS server: " . $res->status_line . ".\n"; }

    # didn't get the response we wanted? die.
    if ($res->content !~ /can be found <A HREF=([^>]*)>here<\/A>/i) {
       die "Response from TTS server not understood. Odd.\n"; }

    # side effect of error checking above is to set $1 to
    # the actual wav file that was generated. this is good.
    my $wav_url  = "http://morrissey.naturalvoices.com$1";
    my $wav_file = $1; # for use in saving to disk.
    $wav_file =~ s/.*?\/(\w+.wav)/$1/;
    getstore($wav_url, "$wav_file") or
     die "Download of $wav_file failed: $!";
    push(@temporary_wav_files, $wav_file);

# with all our files downloaded, play them in
# order with the Win32::Sound module. else, they
# just sit there in hopes of the user playing them.
print  "Playing downloaded wav files...\n";
foreach my $temporary_wav_file (@temporary_wav_files) {
    print " Now Playing: $temporary_wav_file\n";


O'Reilly Home | Privacy Policy

© 2007 O'Reilly Media, Inc.
Website: | Customer Service: | Book issues:

All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.