aboutsummaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rwxr-xr-xbin/mpdlrc154
-rwxr-xr-xbin/mpdlrc-notify-send16
2 files changed, 170 insertions, 0 deletions
diff --git a/bin/mpdlrc b/bin/mpdlrc
new file mode 100755
index 0000000..b127c1c
--- /dev/null
+++ b/bin/mpdlrc
@@ -0,0 +1,154 @@
+#!/usr/bin/env perl
+
+#
+# mpdlrc -- Print timed lyrics from an LRC file for MPD's currently playing
+# song line-by-line to stdout. See README.markdown.
+#
+# Author: Tom Ryder <tom@sanctum.geek.nz>
+# Copyright: 2015
+#
+package Sanctum::Mpdlrc;
+
+# Force me to write this properly
+use strict;
+use warnings;
+use utf8;
+use autodie qw(:all);
+
+# Require a few modules
+use Carp;
+use Const::Fast;
+use Net::MPD;
+use Time::HiRes qw(sleep);
+
+# Require at least Perl 5.12
+use 5.012;
+
+# Specify version number
+our $VERSION = 0.1;
+
+# Specify some constants to appease Perl::Critic
+const my $SECONDS_PER_MINUTE => 60;
+const my $HUNDREDTHS_PER_SECOND => 100;
+
+# Connect to MPD, or give up and cry
+my $mpd = Net::MPD->connect()
+ or croak('Failed to connect to MPD');
+
+# We declare the PID outside of the main loop so we can kill it on subsequent
+# iterations of the loop, should we need to restart the process.
+my $pid;
+
+# Use UTF-8 for output, because forëigñ charåcters āre ìmpørtánt
+binmode STDOUT, ':encoding(utf8)';
+STDOUT->autoflush(1);
+
+# Loop waiting for MPD events
+MPD: while (1) {
+
+ # Get the current status
+ my $status = $mpd->update_status();
+
+ # If there's a song playing, we'll try and spit some lyrics
+ if ( $status->{state} eq 'play' ) {
+
+ # Get details about the current song
+ my $song = $mpd->current_song();
+
+ # Fork a new process
+ $pid = fork;
+
+ # This block should only be run by the fork
+ if ( !$pid ) {
+
+ # Build the expected filename for the lyric file from the song's
+ # author and title
+ my $lfn = sprintf '%s/.lyrics/%s - %s.lrc', $ENV{HOME},
+ @{$song}{qw(Artist Title)};
+
+ # If no such file exists, we have failed
+ if ( !-e $lfn ) {
+ exit 1;
+ }
+
+ # Read a lyrics queue object from the file, providing it with the
+ # elapsed time (i.e. telling it how far into the song we already
+ # are)
+ my $lyrics = read_lyrics_queue( $lfn, $status->{elapsed} );
+
+ # Step through the lyrics queue object, sleeping the required
+ # amount of time before printing each line of text to stdout
+ foreach my $lyric ( @{$lyrics} ) {
+ sleep $lyric->{delay};
+ printf {*STDOUT} "%s\n", $lyric->{text};
+ }
+
+ # We, the fork, are done!
+ exit;
+ }
+ }
+
+ # Wait for something else to happen to the player, whether or not there's a
+ # forked process going
+ $mpd->idle('player');
+
+ # Something important happened; kill any running lyric processes
+ if ($pid) {
+ kill 'INT', $pid;
+ }
+}
+
+# Subroutine to read lyrics from the given filename and return a queue object
+# specifying a list of lyrics to display and how long to wait before displaying
+# each line
+sub read_lyrics_queue {
+ my ( $lfn, $elapsed ) = @_;
+ $elapsed //= 0;
+
+ # Read the file into a list of lines
+ open my $lfh, q{<:encoding(utf8)}, $lfn;
+ my @lines = readline $lfh;
+ close $lfh;
+
+ # Start a list of lyric hashrefs
+ my @lyrics;
+
+ # Read each line
+ LINE: foreach my $line (@lines) {
+
+ # Get rid of trailing newlines
+ chomp $line;
+
+ # If the line is in LRC format, we'll queue it up
+ if ( $line =~ m{\[(\d+):(\d+)[.](\d+)\](.+)}msx ) {
+
+ # Read minutes, seconds, hundredth-seconds, and text from the
+ # matches in the line
+ my ( $min, $sec, $hsec, $text ) = ( $1, $2, $3, $4 );
+
+ # Flatten out the times into a number of seconds, fractional
+ # (that's why we need sleep() from Time::HiRes)
+ my $tsec =
+ ( $min * $SECONDS_PER_MINUTE ) +
+ $sec +
+ ( $hsec / $HUNDREDTHS_PER_SECOND );
+
+ # If the lyric is yet to be displayed, i.e. we haven't already
+ # passed the appropriate point in the song, queue it up and
+ # increment the elapsed time for queuing up the next lyric, if any
+ if ( $tsec > $elapsed ) {
+ my $lyric = {
+ delay => $tsec - $elapsed,
+ text => $text,
+ };
+ push @lyrics, $lyric;
+ $elapsed += $lyric->{delay};
+ }
+
+ }
+ }
+
+ # Return a reference to the built lyric object
+ return \@lyrics;
+}
+
diff --git a/bin/mpdlrc-notify-send b/bin/mpdlrc-notify-send
new file mode 100755
index 0000000..977cf1b
--- /dev/null
+++ b/bin/mpdlrc-notify-send
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+# Check we have the two programs we need
+hash mpdlrc || exit
+hash notify-send || exit
+
+# Read the priority and timeout from the environment, or set default values
+priority=${MPDLC_PRIORITY:-low}
+timeout=${MPDLC_TIMEOUT:-2000}
+
+# Loop over each line output by mpdlrc (which will need to be somewhere in your
+# PATH) and pass it to notify-send(1)
+while IFS= read -r lyric ; do
+ notify-send -u "$priority" -t "$timeout" "$lyric"
+done < <(mpdlrc)
+