This was inspired by Which Perl modules to use to display karaoke lyrics?. After a bit of thought, I figured out how to get the output of timidity through IPC::Open3. You need to open a shell, and let it execute timidity. Instead of opening timidity itself.

The method of opening a shell, lets you stop play of a kar file, and start another, without restarting the program.

This script shows a couple of neat tricks, like how to set a background image resized to fullscreen, on a canvas. I use Imager, but have included the equivalent Image::Magick method in comments.

I also didn't spend much time making sure it would run without tweaking on all Xserver screen dimensions. I run 1024x768 and things work well. If you have a smaller screen, you will have to adjust the "big font" size, and adjust the upward scrolling line at

$canvas->move('text', 0, -45);

There may be a few other tweaks needed on a smaller screen too, but this should be a pretty good basis for how to do this.

There is also the problem of "readbility of the text as it scrolls upward". I have no trouble following it, but some people may like the lyrics presented in a longer line, instead of word by word. I tried making a tempstring which nicely slows down the scrolling, but I've noticed that it occaisionally lags behind the music. The code I tried is

$tempstring .= $buf; if($tempstring =~ /\n/){ my @temps = split(/\n/,$tempstring); write_lyric("$temps[0] $temps[1]\n"); $tempstring = ''; }
But you may know a better way.
#!/usr/bin/perl use warnings; use strict; use Tk; use Tk::JPEG; use IPC::Open3; use Imager; use MIME::Base64; local *IN; local *OUT; local *ERR; $|=1; my $imagenam = shift || '1Zen16.jpg' or die "need a jpg $!\n"; my $defaultkar = "sloop-john-b.kar"; my $start = 0; # clip off midi header info my $pid = open3(\*IN,\*OUT,\*ERR,'/bin/sh'); my $timpid; #used to stop timidity with closing filehandles to sh my $mw = new MainWindow; my ($width,$height) = ( $mw->screenwidth(), $mw->screenheight() ); #################################################### my $imagein = Imager->new(); my $imageout; $imagein->open(file=>$imagenam, type=>'jpeg') or die $imagein->errstr( +); my $newimg = $imagein->scale(xpixels=>$width, ypixels=>$height, type=> +'min'); $newimg->write(type => 'jpeg', data => \$imageout ); my $imageenc = encode_base64($imageout) or die $!; my $image = $mw->Photo(-data => $imageenc); #################################################### #in case you want to use Image Magick # use Image::Magick; # my $im = Image::Magick->new; # $im->Read($imagenam); # $im->Scale( geometry => $width.'x'.$height); # my $imageout = $im->ImageToBlob(); # my $imageenc = encode_base64($imageout) or die $!; # my $image = $mw->Photo(-data => $imageenc); ######################################################## $mw->fontCreate('big', -family=>'courier', -weight=>'bold', -size=>int(-32*32/18)); my $topframe = $mw->Frame(-bg => 'steelblue')->pack(-fill =>'x'); my $lab = $topframe->Label( -text=> 'Enter Kar file to play: ')->pack(-side=>'left'); my $entry=$topframe->Entry(-width => 80)->pack(-side=>'left'); $entry->bind('<Return>', \&send_to_shell ); $topframe->Button(-text => 'Ok', -command => \&send_to_shell)->pack(-side=>'left'); $topframe->Button(-text => 'Exit', -command => sub{exit})->pack(-side=>'right',-padx=>10 +); my $stopbut = $topframe->Button(-text => 'Stop', -state => 'disabled', -command => \&send_stop)->pack(-side=>'left',-padx=>10 +); my $canvas = $mw->Canvas( -width => $width, -height => $height, -bg=>'black', )->pack(-expand => 1, -fill=>'both'); my $tile = $canvas->createImage(0,0, -image=> $image, -anchor => 'nw', ); $mw->fileevent(\*OUT,'readable',\&get_from); MainLoop; sub send_to_shell { $canvas->delete('text'); my $cmd=$entry->get() || $defaultkar; $cmd = "timidity $cmd"; print IN "$cmd\n"; $stopbut->configure(-state => 'normal'); $entry->configure(-state => 'disabled'); } sub get_from { my $buf=''; sysread OUT,$buf,1024; if($buf =~ /Title/){ $start = 1; #hack to get first word of actual song lyrics my @words=split(/\s+/,$buf); $buf = pop @words; } if( $start == 0){ return} $buf =~ tr/\n/ /; write_lyric("$buf\n"); } sub write_lyric { my $str = shift; $canvas->createText($width/2, $height-50, -fill => 'hotpink', -font => 'big', -text => $str, -tags => ['text'], ); $canvas->move('text', 0, -45); } sub send_stop{ my $timgrep = `ps -a | grep timidity`; ($timpid) = $timgrep =~ /(\d+)/; system("kill $timpid"); $stopbut->configure(-state =>'disabled'); $entry->configure(-state => 'normal'); $start = 0; } __END__

Replies are listed 'Best First'.
Re: Tk-Karoake Player-w-timidity
by Anonymous Monk on Aug 02, 2010 at 16:39 UTC
    Hi Zentara! (its me renegadex) just wanted to share to you a Gtk2 version of your karaoke player. its not yet finish but atleast i was able to make it do the thing that you do (display lyrics on the go). this is the code i used (borrowed from some examples etc).
    #!/usr/bin/perl # modules use strict; use Gtk2 '-init'; use Gtk2::Helper; use Image::Magick; use Goo::Canvas; use Data::Dumper; use IPC::Open2; use FileHandle; my $wfh = FileHandle->new(); my $rfh = FileHandle->new(); my $text; my $timpid; my $tag; $|=1; # i dont know what this is???? open2($rfh,$wfh, "/bin/sh"); my $tag = Gtk2::Helper->add_watch($rfh->fileno(), 'in',\&get_from); my $window = Gtk2::Window->new(); $window->fullscreen(); my $canvas = Goo::Canvas->new(); config_canvas(); $window->add($canvas); $window->show_all(); Gtk2->main(); sub config_canvas { my $res_line = `xdpyinfo | grep 'dimensions:'`; my ($dw,$dh) = $res_line =~ m/(\d*)x(\d*) pixel/; my $uw = $dw - 50; my $uh = $dh - 50; $canvas->set_bounds(-$uw/2,-$uh/2,$uw/2,$uh/2); $canvas->set('background-color'=>"black"); $canvas->set('anchor','center'); $canvas->set('integer-layout'=>1); my $root = $canvas->get_root_item; my $bkg = Goo::Canvas::Group->new($root); my $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file_at_scale('karaok +e/b1.jpg',$uw,$uh,1); my $img = Goo::Canvas::Image->new($bkg,$pixbuf,-$uw/2,-$uh/2); $img->signal_connect('button_press_event'=>\&mouse_press); my $video = Goo::Canvas::Group->new($root); my $lyrics = Goo::Canvas::Group->new($root); $text = Goo::Canvas::Text->new($lyrics,"testing",0,0,0,'center +'); } sub get_from { # we safely can read a chunk into $buffer my $buffer; if ( not sysread($rfh, $buffer, 1024) ) { # obviously the connected pipe was closed print "exit\n"; Gtk2::Helper->remove_watch ($tag) or die "couldn't remove watc +her"; close($rfh); return 1; } # do something with $buffer ... print $buffer . "\n"; # *always* return true return 1; } sub mouse_press { my ($widget, $target, $event) = @_; if($event->button == 1){ print "start\n"; send_to_shell(undef,'Just Beat It','/home/server/karaoke/sloop +-john-b.kar'); }else{ print "stop\n"; send_stop(); } } sub send_to_shell { my ($widget,$title,$file) = @_; print "Playing.. $title\n"; my $cmd = "timidity '$file'"; print $wfh "$cmd\n"; } sub send_stop{ my $timgrep = `ps -a | grep timidity`; ($timpid) = $timgrep =~ /(\d+)/; system("kill $timpid"); }
    some of the modules here are not yet being used. anyway... im also here to ask you if there is a way to extract lyrics from a .kar file? i saw kmid2 and it was able to display the whole lyrics, and i want to know how that is made. tnx!
              my $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file_at_scale('karaoke/b1.jpg',$uw,$uh,1);

      Hi, since this is a pixbuf that is integral to the program, you might want to include it in your script as a bas64encoded file. Like in Re: PerlMagick Gtk2::Gdk::Pixbuf


      I'm not really a human, but I play one on earth.
      Old Perl Programmer Haiku
        just wanna contribute something in your karaoke project.. this code requires perl-MIDI-Perl to run. I got it from cpan, its not available on the standard fedora13 repo. anyway here is a code to be able to extract lyrics of a midi file...
        sub send_to_shell { my ($widget,$file) = @_; my $string; my $opus = MIDI::Opus->new({ 'from_file' => $file, 'exclusive_event_callback' => sub{ my $temp = $_[2]; chomp $temp; if($_[0] eq 'text_event'){ if($_[1] == 0){ }else{ $string = $string . $_[2]; push @arr_lyr, $_[2]; } }elsif($_[0] eq 'track_name'){ $marker = $_[2]; } }, 'include' => \@MIDI::Event::All_events, 'exclude' => \@MIDI::Event::MIDI_events, }); @line_lyrics = split(/\/|\\/,$string); shift @line_lyrics; print "Marker: $marker\n"; print "_____START_____\n"; $start = 0; # start song my $cmd = "timidity '$file'"; print $wfh "$cmd\n"; }
        enjoy! you can use this to replace the same sub routine on my previous post.
        Mabuhay Civil Engineers! :D
        just a question regarding the base64encoded. what is the difference if i just simply use this:
        my $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file_at_scale('karaoke/b1.jpg +',$uw,$uh,1);
        instead of this:
        my $pixbuf = do { my $loader = Gtk2::Gdk::PixbufLoader->new(); $loader->write( $bunny ); $loader->close(); $loader->get_pixbuf(); };