#!/usr/bin/perl -w use strict; use IPC::Open2; use IO::Prompt; my $FLAC_FILE = shift; my $TRACK = shift || 1; # get the track start positions from the embedded cuesheet open my $CUESHEET, "metaflac --export-cuesheet-to - $FLAC_FILE |"; my @position_for_track; my $i = 1; while (<$CUESHEET>) { if (my ($m,$s,$f) = /INDEX 01 (\d\d):(\d\d):(\d\d)/) { $position_for_track[$i] = ($m * 60) + $s + ($f / 75); $i++; } } close $CUESHEET; my $track_count = scalar @position_for_track - 1; print "$track_count tracks\n"; die "There is no track $TRACK" if $TRACK < 1 or $TRACK > $track_count; # launch the flac123 player as a daemon my $flac123_pid = open2(my $PLAYER_OUT, my $PLAYER_IN, qw{flac123 -R}); print "flac123 PID = $flac123_pid\n"; my $current_track = $TRACK; my $next_track_start = $position_for_track[$current_track + 1]; print "Track $current_track\n"; my %commands = ( z => sub { return if $current_track <= 1; $current_track--; my $position = $position_for_track[$current_track]; print $PLAYER_IN "JUMP $position\n"; print "Track $current_track\n"; }, x => sub {}, c => sub {}, v => sub {}, b => sub { return if $current_track >= $track_count; # note, don't increment or print $current_track here, # since it will be automatically detected by the other # process watching the output of flac123 my $position = $position_for_track[$current_track + 1]; print $PLAYER_IN "JUMP $position\n"; }, q => sub { print $PLAYER_IN "QUIT\n"; exit; }, ); # track has advanced automatically $SIG{USR1} = sub { # detect track change my $time = read_time(); if (defined $next_track_start && $time >= $next_track_start) { $current_track++; $next_track_start = $position_for_track[$current_track + 1]; print "Track $current_track\n"; } }; my $parent_pid = $$; my $pid = fork; if ($pid) { # parent while (my $cmd = prompt('', -one_char, -echo => '')) { if (exists $commands{$cmd}) { $commands{$cmd}->(); } } } elsif (defined $pid) { # child print $PLAYER_IN "LOAD $FLAC_FILE\n"; print $PLAYER_IN "JUMP $position_for_track[$TRACK]\n"; while (<$PLAYER_OUT>) { if (/\@F (\d+) (\d+) (\d+\.\d\d) (\d+\.\d\d)/) { my ($frame, $frames_left, $time, $time_left) = ($1, $2, $3, $4); # note the current time and alert the parent write_time($time); kill 'USR1', $parent_pid; } } waitpid $flac123_pid, 0; } else { die "Unable to fork: $!"; } sub write_time { my ($time) = @_; open my $TIME, '>', 'current_time'; print $TIME "$time\n"; close $TIME; } sub read_time { open my $TIME, '<', 'current_time'; my $time = <$TIME>; close $TIME; chomp $time; return $time; } =begin flac123 -R outputs @R FLAC123 flac123 tagline. Output at startup. @I ID3: Prints out the metadata information after loading the flac file. a = title (30 chars) b = artist (30 chars) c = album (30 chars) d = year (4 chars) e = comment (30 chars) f = genre (30 chars) @I filename Prints out the filename of the flac file, minus the extension. Happens after a flac file has been loaded and there is no metadata available. @F Frame decoding status updates (once per frame). Current-frame and frames-remaining are integers; current-time and time-remaining floating point numbers with two decimal places. @P {0, 1, 2} Stop/pause status. 0 - playing has stopped. When 'STOP' is entered, or the flac file is finished. 1 - Playing is paused. Enter 'PAUSE' or 'P' to continue. 2 - Playing has begun again. =cut