#!/usr/bin/perl -w
# ------------------------------------------------------------------------------
# Simple announcement script for SlimServer using the CLI                 v0.8
#
# Copyright (c) 2003-2007 Felix Mueller (felix.mueller(at)gwendesign(dot)com)
#               2008-2018 Juergen Edner (juergen(at)eisfair(dot)org)
#
# This file might be distributed under the same terms as the SlimServer
# (http://www.slimdevices.com/)
#
# Description: Plays a sound and shows a message interrupting the current song
#
#              - Check power state and decide if message should be send
#                - Store current parameters
#                - The sound is appended to the current playlist and played
#                - After the duration of the sound the sound is removed
#                - Resume playing the current song
#                - Restore current parameters
#
# Plattform tested: Linux
#
# Known restrictions: - trancoded files (i.e. ogg) will restart at the beginning
#                       after the announcement
#                     - a shuffled playlist will be reshuffled
#                     - does not work with iTunes (maybe works only with real
#                       SLIMP3)
#
# History: - v0.8 - Fixed some 'uninitialized parameter' messages
#            v0.7 - Added new command line parameter: --volinc --debug --poweron
#                 - Added support for muted volumes
#            v0.6 - Added parameters for user/password (thanks to Mario Wagner)
#            v0.5 - Adapted and tested with SlimServer 5.1.1
#            v0.4 - Added new command line parameter: --name --server --ip
#                   --line1 --line2 --sound
#                 - Added player selection by name
#            v0.3 - Added power and volume persistency
# ------------------------------------------------------------------------------

use strict;

use Getopt::Long;
use IO::Socket;

#===============================================================================
# main
#===============================================================================

# Parameter defaults overridden by command line
my $szServerIP      = "127.0.0.1";   # ip address of SlimServer
my $iServerCLIPort  = 9090;          # port number of SlimServer CLI
my $szPlayerName    = "";            # name of Squeezebox
my $szLine1         = "Line1 text";  # text which should be displayed in line 1
my $szLine2         = "Line2 text";  # text which should be displayed in line 2
my $szSound         = "/var/slims/signals/signal_two_times.mp3";  # path on the machine where SlimServer runs
my $szUser          = "";            # username for SlimServer authentication
my $szPassword      = "";            # password for SlimServer authentication
my $iVolumeIncrease = 20;            # value by which the current volume should be increased
my $iAutoPowerOn    = 1;             # 0 = off, 1 = on

# Internal parameters
my $iDebug          = 0;             # 0 = off, 1 = on

GetOptions(
    "server:s"  => \$szServerIP,
    "port:i"    => \$iServerCLIPort,
    "name:s"    => \$szPlayerName,
    "line1:s"   => \$szLine1,
    "line2:s"   => \$szLine2,
    "user:s"    => \$szUser,
    "pass:s"    => \$szPassword,
    "sound:s"   => \$szSound,
    "volinc:i"  => \$iVolumeIncrease,
    "poweron:i" => \$iAutoPowerOn,
    "debug:i"   => \$iDebug
    );

# Check for a player name
showUsage() if( $szPlayerName eq "" );

$iAutoPowerOn    = 1 if( $iAutoPowerOn > 1 );
$iDebug          = 1 if( $iDebug > 1 );

# Prepare variables
$szPlayerName = encodeChars( $szPlayerName );
$szLine1      = encodeChars( $szLine1 );
$szLine2      = encodeChars( $szLine2 );
$szSound      = encodeChars( $szSound );

# Try to open a connection to the SLIMP3 CLI
my $socket = IO::Socket::INET->new(
    PeerAddr => $szServerIP,
    PeerPort => $iServerCLIPort,
    Proto    => "tcp",
    Type     => SOCK_STREAM
    );

# Check if socket was opened successful
if( !defined( $socket )){
    print "Cannot connect to $szServerIP:$iServerCLIPort\n";
    exit;
}

# Login to Squeezebox
if( $szUser ne "" ){
    my $login = sendAndReceive( "login $szUser $szPassword" );
    $iDebug && print "user: $szUser, pass: $szPassword\n";
}

# Get the # of players
my $iNumPlayer = sendAndReceive( "player count ?" );

if ( !defined( $iNumPlayer ) ){
    $iNumPlayer = 0;
}

$iDebug && print "iNumPlayer: $iNumPlayer\n";

# Get the player ip and port from the name
my $szPlayerIPAndPort = "";
for( my $i = 0; $i < $iNumPlayer; $i++ ){
    my $szPlayerNameRead = sendAndReceive( "player name $i ?" );
    $iDebug && print "szPlayerNameRead:$szPlayerNameRead:\n";

    if( $szPlayerName eq $szPlayerNameRead ){
        $szPlayerIPAndPort = sendAndReceive( "player address $i ?" );
        last;
    }
}
$iDebug && print "szPlayerName....:$szPlayerName:\n";

# Check if successful
if( $szPlayerIPAndPort eq "" ){
    print "Player with name: $szPlayerName could not be found!\n";
    close( $socket );
    exit;
}
$iDebug && print "szPlayerIPAndPort: $szPlayerIPAndPort\n";

# Get power status (on, off)
my $iPower = sendAndReceive( "power ?" );
$iDebug && print "iPower: $iPower\n";
$iDebug && print "iAutoPowerOn: $iAutoPowerOn\n";

if( $iPower == 1 || ( $iPower == 0 && $iAutoPowerOn == 1 )){
    # go on...
    $iDebug && print "Power parameters ok, message will be send!\n";

    # Get shuffle status (on, off)
    my $iShuffle = sendAndReceive( "playlist shuffle ?" );
    $iDebug && print "iShuffle: $iShuffle\n";

    # Turn shuffle off
    sendAndReceive( "playlist shuffle 0" );

    # Get mode (play, stop, pause)
    my $iMode = sendAndReceive( "mode ?" );
    $iDebug && print "iMode: $iMode\n";

    # Get current song
    my $iIndex = sendAndReceive( "playlist index ?" );
    $iDebug && print "iIndex: $iIndex\n";

    # Get path and filename to current song
    my $iPath = sendAndReceive( "playlist path $iIndex ?" );
    $iDebug && print "iPath: $iPath\n";

    # Check the song file type (mp3, ogg, wav)
    my $iMP3 = 0;
    $iMP3 = 1 if( $iPath =~ m/mp3$/ );
    $iDebug && print "iMP3: $iMP3\n";

    # Get the current position (in s)
    my $iTime = sendAndReceive( "time ?" );
    $iDebug && print "iTime: $iTime\n";

    # Stop the current song
    sendAndReceive( "stop" );

    # Add the announcement sound file at the end of the playlist
    sendAndReceive( "playlist append $szSound" );
    $iDebug && print "szSound: $szSound\n";

    # Get the number of tracks in the playlist
    my $iTracks = sendAndReceive( "playlist tracks ?" );
    $iTracks -= 1;
    $iDebug && print "iTracks: $iTracks\n";

    # Is now -1 if playlist was empty
    $iTracks = 0 if( $iTracks < 0 );

    # Get the volume
    my $iVolume = sendAndReceive( "mixer volume ?" );
    $iDebug && print "iVolume: $iVolume\n";
    $iDebug && print "iVolumeIncrease: $iVolumeIncrease\n";

    # Calculate new volume value
    my $iVolumeNew;
    if( $iVolume < 0 ){
        # value negativ -> sound is muted
        $iVolumeNew = $iVolume * -1 + $iVolumeIncrease;
    } else {
        # value >= 0 -> sound not muted
        $iVolumeNew = $iVolume + $iVolumeIncrease;
    }
    $iVolumeNew = 100 if( $iVolumeNew > 100 );
    $iDebug && print "iVolumeNew: $iVolumeNew\n";

    # Set a volume for the announcement
    sendAndReceive( "mixer volume $iVolumeNew" );

    # Play the announcement sound
    sendAndReceive( "playlist index $iTracks" );

    # Get the duration of the announcement sound
    my $iDuration = sendAndReceive( "playlist duration $iTracks ?" );
    $iDebug && print "iDuration: $iDuration\n";

    sleep(1);

    # Show the announcement message
    sendAndReceive( "display $szLine1 $szLine2 $iDuration" );

    # Wait until announcement sound has finished
    sleep( $iDuration );

    # Stop it
    sendAndReceive( "stop" );

    # Mute output
    sendAndReceive( "mixer volume 0" );
  # sendAndReceive( "mixer muting 1" );

    # Remove the announcement sound from the playlist
    sendAndReceive( "playlist delete $iTracks" );

    # Reselect the old song
    sendAndReceive( "playlist index $iIndex" );

    # Set the player in the old mode
    if( $iMode eq "stop" ){
        sendAndReceive( "stop");
    }
    elsif( $iMode eq "pause"){
        if( $iMP3 == 1 ){
            sendAndReceive( "time $iTime" );
            sleep(1);
        }
        sendAndReceive( "mode pause" );
    }
    elsif( $iMode eq "play"){
        if( $iMP3 == 1 ){
            sendAndReceive( "time $iTime" );
        }
    }

    # Reinstate the volume
    if( $iVolume < 0 ){
        # value negativ -> sound is muted
        # 1. set positive volume
        $iVolume = $iVolume * -1;
        sendAndReceive( "mixer volume $iVolume" );
        # 2. mote volume
        sendAndReceive( "mixer muting 1" );
    } else {
        # value positive
        sendAndReceive( "mixer volume $iVolume" );
    }
    
    # Reinstate the shuffle mode
    sendAndReceive( "playlist shuffle $iShuffle" );

    # Reinstate the power mode
    sendAndReceive( "power $iPower" );
} else {
    $iDebug && print "Power parameters disabled, message will not be send!\n";
}

close( $socket );

#===============================================================================
# end main
#===============================================================================
# ------------------------------------------------------------------------------
sub sendAndReceive
{
    my $cmd = shift;

    return if( $cmd eq "" );

#   $cmd = $szPlayerIP . encodeChars( ":") . $iPlayerPort . " " . $cmd;

    if(( defined $szPlayerIPAndPort ) && ( $szPlayerIPAndPort ne "" )){
        $cmd = $szPlayerIPAndPort . " " . $cmd;
    }
    print $socket "$cmd\n";

    my $answer = <$socket>;
    $answer =~ s/$cmd //i if defined $answer;
    $answer =~ s/\n// if defined $answer;

    return $answer;
}

# ------------------------------------------------------------------------------
sub encodeChars
{
    my $myChars = $_[0];

    my $MetaChars = quotemeta(' ;,/?\|=+)(*&^%$#@!~`:');
    
    $myChars =~ s/([$MetaChars\"\'\x80-\xFF])/"%" . uc( sprintf( "%2.2x", ord($1)))/eg;

    return $myChars;
}

# ------------------------------------------------------------------------------
sub showUsage
{
    print "usage: $0 <parameters>\n";
    print "\n";
    print "Mandatory parameter:\n";
    print "--name=<player name>\ti.e.: \"my slimp3\"\n";
    print "\n";
    print "Optional parameters:\n";
    print "--server=<ip>\t\tdefault: $szServerIP\n";
    print "--port=<nr>\t\tdefault: $iServerCLIPort\n";
    print "--line1=<text>\t\tdefault: \"$szLine1\"\n";
    print "--line2=<text>\t\tdefault: \"$szLine2\"\n";
    print "--user=<text>\t\tdefault: \"$szUser\"\n";
    print "--pass=<text>\t\tdefault: \"$szPassword\"\n";
    print "--sound=<soundfile>\tdefault: $szSound\n";
    print "--volinc=<inc.volume>\tdefault: $iVolumeIncrease\n";
    print "--poweron=<0|1>\t\tdefault: $iAutoPowerOn\n";
    print "--debug=<0|1>\t\tdefault: $iDebug\n";
    exit;
}

# ------------------------------------------------------------------------------
