#!/usr/bin/perl -w use Time::HiRes qw(gettimeofday tv_interval); use File::Basename; use Net::DBus; use Net::DBus::Reactor; use Net::DBus::Callback; sub find_lrc; sub parse_lrc; sub parse_lrc_line; sub resume_song; sub song_started; sub song_ended; sub song_paused; sub song_unpaused; sub song_seeked; sub debug; my $bus = Net::DBus->session; my $quodlibet = $bus->get_service("net.sacredchao.QuodLibet"); my $notify = $quodlibet->get_object("/net/sacredchao/QuodLibet/DBusSignalFull", "net.sacredchao.QuodLibet"); my $current = undef; my $current_song = undef; my $current_line = -1; my $timer = undef; my $timer_time = undef; my $next_time = undef; my $playing = 0; my $start_time = undef; my $pause_time = undef; my $loop = Net::DBus::Reactor->main(); $notify->connect_to_signal( "SongStarted", sub { my ($song) = @_; $current_song = $song; song_started($$song{'~filename'}, $$song{title} || '', $$song{artist} || '', $$song{album} || ''); }); $notify->connect_to_signal( "SongEnded", sub { my ($song, $stopped) = @_; song_ended($$song{'~filename'}); $current_song = undef; }); $notify->connect_to_signal( "SongPaused", sub { song_paused(); }); $notify->connect_to_signal( "SongSeeked", sub { my ($song, $ms) = @_; song_seeked($$song{'~filename'}, $ms); }); $notify->connect_to_signal( "SongUnpaused", sub { my $song = $current_song; song_unpaused($$song{'~filename'}, $$song{title} || '', $$song{artist} || '', $$song{album} || ''); }); $loop->run(); sub song_started { my ($file, $title, $artist, $album) = @_; $start_time = gettimeofday; debug("# SongStarted\n"); $playing = 1; $current = find_lrc($file); $current_line = -1; if ($timer) { $loop->remove_timeout($timer); $timer = undef; } $next_time = undef; $timer_time = undef; resume_song(); if (not $title) { $title = basename $file; $title =~ s/\.[^.]+$//; } system("tune.py", $title, $artist, $album); } sub song_ended { my ($file) = @_; debug("# SongEnded\n"); $playing = 0; if ($timer) { $loop->remove_timeout($timer); $timer = undef; system "tune.py"; } } sub song_paused { $current_line--; debug("# Paused\n"); $playing = 0; $pause_time = gettimeofday; if ($timer) { $loop->remove_timeout($timer); $timer = undef; # check how much time we have slept already my $elapsed = tv_interval($timer_time) * 1000; debug("# elapsed %d\n", $elapsed); $next_time -= $elapsed; } system "tune.py"; } sub song_seeked { my ($song, $ms) = @_; debug("# SongSeeked $ms\n"); if ($timer) { $loop->remove_timeout($timer); $timer = undef; } if ($current) { for (0..@$current - 1) { my $line = $$current[$_]; my ($t, $lyric) = @$line; if ($t >= $ms) { debug("# Closest line is %d - %s\n", $t, $lyric); $next_time = $t - $ms; $current_line = $_ - 1; resume_song(); if ($_ != 0) { my $last_line = $$current[$_ - 1]; printf "%s\n", $$last_line[1]; } last; } else { $ms -= $t; } } } } sub song_unpaused { my ($file, $title, $artist, $album) = @_; $start_time += $pause_time - gettimeofday if $pause_time; debug("# UnPaused\n"); $playing = 1; resume_song(); if (not $title) { $title = basename $file; $title =~ s/\.[^.]+$//; } system("tune.py", $title, $artist, $album); } sub find_lrc { my ($file) = @_; my $lyric_file = "$file.lrc"; debug("# Switching to %s\n", $file); if (-r $lyric_file) { return parse_lrc $lyric_file; } return undef; } sub parse_lrc { my ($lrc) = @_; return undef unless open my $fh, "<$lrc"; my @lines = <$fh>; my @line = (); local $last = 0; for (@lines) { my $l = parse_lrc_line $_; push @line, $l if $l; } close $fh or return undef; return \@line; } sub parse_lrc_line { my ($line) = @_; if ($line =~ /\[(\d+):(\d+)(\.(\d\d))?\](.*)/) { my $min = $1; my $sec = $2; my $us = $4 || 0; my $lyric = $5; my $t = $min * 60 * 1000 + $sec * 1000 + $us * 10; my @ret = ($t - $last, $lyric); $last = $t; return \@ret; } return undef; } sub queue_next_line { if ($timer) { $loop->remove_timeout($timer); $timer = undef; } if (++$current_line == @$current) { return; } my $line = $$current[$current_line]; my ($t, $lyric) = @$line; if ($next_time) { $t = $next_time; } else { $next_time = $t; } debug("# scheduling line in %d\n", $t); $timer = $loop->add_timeout( $t, Net::DBus::Callback->new( method => sub { printf "%s\n", $lyric; $next_time = undef; queue_next_line(); })); $timer_time = [gettimeofday]; } sub resume_song { return if not $current; return if $timer; return if not $playing; queue_next_line(); } sub debug { return; if ($start_time) { my $s = $#_ > 0 ? sprintf($_[0], @_[1..$#_]) : $_[0]; printf "# %d - %s", gettimeofday - $start_time, $s; } else { printf(@_); } }