|
@@ -6,26 +6,28 @@ use File::Basename;
|
|
|
use Time::HiRes qw( sleep );
|
|
use Time::HiRes qw( sleep );
|
|
|
use File::Temp qw( tempfile );
|
|
use File::Temp qw( tempfile );
|
|
|
use URI::Escape;
|
|
use URI::Escape;
|
|
|
|
|
+use JSON;
|
|
|
|
|
|
|
|
# Prototypes
|
|
# Prototypes
|
|
|
-sub xbmcHTTP($);
|
|
|
|
|
|
|
+sub buildCmd($$);
|
|
|
|
|
+sub xbmcJSON($$);
|
|
|
|
|
+sub printHash($$);
|
|
|
|
|
|
|
|
# App config
|
|
# App config
|
|
|
|
|
+my $BASE_URL = 'http://localhost:3005/jsonrpc?request=';
|
|
|
my $TEMP_DIR = `getconf DARWIN_USER_TEMP_DIR`;
|
|
my $TEMP_DIR = `getconf DARWIN_USER_TEMP_DIR`;
|
|
|
chomp($TEMP_DIR);
|
|
chomp($TEMP_DIR);
|
|
|
my $DATA_DIR = $TEMP_DIR . '/plexMonitor';
|
|
my $DATA_DIR = $TEMP_DIR . '/plexMonitor';
|
|
|
|
|
|
|
|
-# xbmcHTTP commands
|
|
|
|
|
-my %CMDS = (
|
|
|
|
|
- 'PLAYING' => 'GetCurrentlyPlaying',
|
|
|
|
|
- 'GUI' => 'GetGuiStatus'
|
|
|
|
|
-);
|
|
|
|
|
-
|
|
|
|
|
# Debug
|
|
# Debug
|
|
|
my $DEBUG = 0;
|
|
my $DEBUG = 0;
|
|
|
if ($ENV{'DEBUG'}) {
|
|
if ($ENV{'DEBUG'}) {
|
|
|
$DEBUG = 1;
|
|
$DEBUG = 1;
|
|
|
}
|
|
}
|
|
|
|
|
+my $JSON_DEBUG = 0;
|
|
|
|
|
+if ($ENV{'JSON_DEBUG'}) {
|
|
|
|
|
+ $JSON_DEBUG = 1;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
# Command-line arguments
|
|
# Command-line arguments
|
|
|
my ($DELAY) = @ARGV;
|
|
my ($DELAY) = @ARGV;
|
|
@@ -38,35 +40,114 @@ if (!-d $TEMP_DIR) {
|
|
|
die("Bad config\n");
|
|
die("Bad config\n");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-# Mode
|
|
|
|
|
-my $MODE = 'PLAYING';
|
|
|
|
|
-if (basename($0) =~ /GUI/i) {
|
|
|
|
|
- $MODE = 'GUI';
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
# Add the data directory as needed
|
|
# Add the data directory as needed
|
|
|
if (!-d $DATA_DIR) {
|
|
if (!-d $DATA_DIR) {
|
|
|
mkdir($DATA_DIR);
|
|
mkdir($DATA_DIR);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-# Build our file name
|
|
|
|
|
-my $OUT_FILE = $DATA_DIR . '/' . $MODE;
|
|
|
|
|
|
|
+# Build our output file name
|
|
|
|
|
+my $OUT_FILE = $DATA_DIR . '/PLEX';
|
|
|
|
|
|
|
|
# Loop forever (unless no delay is set)
|
|
# Loop forever (unless no delay is set)
|
|
|
-my $data = '';
|
|
|
|
|
-my $dataLast = '';
|
|
|
|
|
|
|
+my %data = ();
|
|
|
|
|
+my %dataLast = ();
|
|
|
do {
|
|
do {
|
|
|
|
|
|
|
|
- # Run the monitor command
|
|
|
|
|
|
|
+ # Only update the file on a change
|
|
|
my $changed = 0;
|
|
my $changed = 0;
|
|
|
- $dataLast = $data;
|
|
|
|
|
- $data = xbmcHTTP($CMDS{$MODE});
|
|
|
|
|
|
|
+ %dataLast = %data;
|
|
|
|
|
+ %data = (
|
|
|
|
|
+ 'playing' => 0,
|
|
|
|
|
+ 'playerid' => -1,
|
|
|
|
|
+ 'time' => 0,
|
|
|
|
|
+ 'title' => '',
|
|
|
|
|
+ 'season', => '',
|
|
|
|
|
+ 'episode', => '',
|
|
|
|
|
+ 'duration', => '',
|
|
|
|
|
+ 'showtitle', => '',
|
|
|
|
|
+ 'thumbnail', => '',
|
|
|
|
|
+ 'file', => '',
|
|
|
|
|
+ 'fanart', => '',
|
|
|
|
|
+ 'album' => '',
|
|
|
|
|
+ 'artist', => '',
|
|
|
|
|
+ 'type' => '',
|
|
|
|
|
+ 'windowid' => '',
|
|
|
|
|
+ 'window' => '',
|
|
|
|
|
+ 'selection' => '',
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ # See if anything is playing
|
|
|
|
|
+ my $result = xbmcJSON('Player.GetActivePlayers', undef());
|
|
|
|
|
+ if (defined($result) && exists($result->{'playerid'})) {
|
|
|
|
|
+ $data{'playing'} = 1;
|
|
|
|
|
+ $data{'playerid'} = $result->{'playerid'} + 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Get details about the playing item
|
|
|
|
|
+ if ($data{'playing'}) {
|
|
|
|
|
+ $result = xbmcJSON('Player.GetProperties', { 'playerid' => $data{'playerid'}, 'properties' => ['time'] });
|
|
|
|
|
+ if (defined($result) && exists($result->{'time'})) {
|
|
|
|
|
+ $data{'time'} = 0;
|
|
|
|
|
+ if (exists($result->{'time'}->{'hours'})) {
|
|
|
|
|
+ $data{'time'} += ($result->{'time'}->{'hours'} * 3600);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (exists($result->{'time'}->{'minutes'})) {
|
|
|
|
|
+ $data{'time'} += ($result->{'time'}->{'minutes'} * 60);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (exists($result->{'time'}->{'seconds'})) {
|
|
|
|
|
+ $data{'time'} += $result->{'time'}->{'seconds'};
|
|
|
|
|
+ }
|
|
|
|
|
+ if (exists($result->{'time'}->{'milliseconds'})) {
|
|
|
|
|
+ $data{'time'} += ($result->{'time'}->{'milliseconds'} / 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $result = xbmcJSON(
|
|
|
|
|
+ 'Player.GetItem',
|
|
|
|
|
+ {
|
|
|
|
|
+ 'playerid' => $data{'playerid'},
|
|
|
|
|
+ 'properties' => [ 'title', 'season', 'episode', 'duration', 'showtitle', 'thumbnail', 'file', 'fanart', 'album', 'artist' ]
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+ if (defined($result) && exists($result->{'item'})) {
|
|
|
|
|
+ foreach my $key ('title', 'season', 'episode', 'duration', 'showtitle', 'thumbnail', 'file', 'fanart', 'album', 'type') {
|
|
|
|
|
+ if (exists($result->{'item'}->{$key})) {
|
|
|
|
|
+ $data{$key} = $result->{'item'}->{$key};
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (exists($result->{'item'}->{'artist'})) {
|
|
|
|
|
+ if (ref($result->{'item'}->{'artist'}) eq 'ARRAY') {
|
|
|
|
|
+ $data{'artist'} = join(', ', @{ $result->{'item'}->{'artist'} });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ warn("Invalid artist array\n");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Get details about the GUI
|
|
|
|
|
+ $result = xbmcJSON('GUI.GetProperties', { 'properties' => [ 'currentcontrol', 'currentwindow' ] });
|
|
|
|
|
+ if (defined($result)) {
|
|
|
|
|
+ if (exists($result->{'currentwindow'})) {
|
|
|
|
|
+ if (exists($result->{'currentwindow'}->{'label'})) {
|
|
|
|
|
+ $data{'window'} = $result->{'currentwindow'}->{'label'};
|
|
|
|
|
+ }
|
|
|
|
|
+ if (exists($result->{'currentwindow'}->{'id'})) {
|
|
|
|
|
+ $data{'windowid'} = $result->{'currentwindow'}->{'id'};
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (exists($result->{'currentcontrol'})) {
|
|
|
|
|
+ if (exists($result->{'currentcontrol'}->{'label'})) {
|
|
|
|
|
+ $data{'selection'} = $result->{'currentcontrol'}->{'label'};
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
# Compare this data set to the last
|
|
# Compare this data set to the last
|
|
|
- if ($data ne $dataLast) {
|
|
|
|
|
- $changed = 1;
|
|
|
|
|
- if ($DEBUG) {
|
|
|
|
|
- print STDERR "Change detected in data:\n" . $data . "\n";
|
|
|
|
|
|
|
+ foreach my $key (keys(%data)) {
|
|
|
|
|
+ if (!exists($dataLast{$key}) || $data{$key} ne $dataLast{$key}) {
|
|
|
|
|
+ $changed = 1;
|
|
|
|
|
+ last;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -77,8 +158,16 @@ do {
|
|
|
|
|
|
|
|
# If anything changed, save the data to disk
|
|
# If anything changed, save the data to disk
|
|
|
if ($changed) {
|
|
if ($changed) {
|
|
|
|
|
+ my $str = '';
|
|
|
|
|
+ foreach my $key (keys(%data)) {
|
|
|
|
|
+ $str .= $key . ' => ' . $data{$key} . "\n";
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($DEBUG) {
|
|
|
|
|
+ print STDERR $str;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
my ($fh, $tmp) = tempfile($OUT_FILE . '.XXXXXXXX', 'UNLINK' => 0);
|
|
my ($fh, $tmp) = tempfile($OUT_FILE . '.XXXXXXXX', 'UNLINK' => 0);
|
|
|
- print $fh $data;
|
|
|
|
|
|
|
+ print $fh $str;
|
|
|
close($fh);
|
|
close($fh);
|
|
|
rename($tmp, $OUT_FILE);
|
|
rename($tmp, $OUT_FILE);
|
|
|
}
|
|
}
|
|
@@ -90,26 +179,95 @@ do {
|
|
|
# Exit cleanly
|
|
# Exit cleanly
|
|
|
exit(0);
|
|
exit(0);
|
|
|
|
|
|
|
|
-sub xbmcHTTP($) {
|
|
|
|
|
- my ($cmd) = @_;
|
|
|
|
|
-
|
|
|
|
|
- #$cmd = '{"jsonrpc":"2.0","method":"Player.GetActivePlayers","id":1}';
|
|
|
|
|
- #$cmd = '{"jsonrpc":"2.0","method":"Player.GetProperties","id":1,"params":{"playerid": 1,"properties": ["time"]}}';
|
|
|
|
|
- $cmd = '{"jsonrpc": "2.0", "method": "Player.GetItem", "params": { "properties": ["title", "album", "artist", "season", "episode", "duration", "showtitle", "tvshowid", "thumbnail", "file", "fanart", "streamdetails"], "playerid": 1 }, "id": "VideoGetItem"}';
|
|
|
|
|
-
|
|
|
|
|
- # Build the command
|
|
|
|
|
- my $url = 'http://localhost:3005/jsonrpc?request=' . uri_escape($cmd);
|
|
|
|
|
- if ($DEBUG) {
|
|
|
|
|
- print STDERR 'Command: ' . $cmd . "\n\t" . $url . "\n";
|
|
|
|
|
|
|
+sub buildCmd($$) {
|
|
|
|
|
+ my ($method, $params) = @_;
|
|
|
|
|
+ if (!defined($method) || length($method) < 1) {
|
|
|
|
|
+ warn('No method name provided');
|
|
|
|
|
+ return undef();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ # Init the command structure
|
|
|
|
|
+ my %cmd = (
|
|
|
|
|
+ 'jsonrpc' => '2.0',
|
|
|
|
|
+ 'id' => '1',
|
|
|
|
|
+ 'method' => $method,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ # Add parameters, if provided
|
|
|
|
|
+ if (defined($params) && ref($params) eq 'HASH') {
|
|
|
|
|
+ $cmd{'params'} = $params;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Return as JSON
|
|
|
|
|
+ return encode_json(\%cmd);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+sub xbmcJSON($$) {
|
|
|
|
|
+ my ($method, $params) = @_;
|
|
|
|
|
+ my $cmd = buildCmd($method, $params);
|
|
|
|
|
+
|
|
|
# Send the request
|
|
# Send the request
|
|
|
- my $data = get($url);
|
|
|
|
|
|
|
+ if ($JSON_DEBUG) {
|
|
|
|
|
+ print STDERR 'Sending command: ' . $cmd . "\n";
|
|
|
|
|
+ }
|
|
|
|
|
+ my $data = get($BASE_URL . uri_escape($cmd));
|
|
|
if (!defined($data)) {
|
|
if (!defined($data)) {
|
|
|
if ($DEBUG) {
|
|
if ($DEBUG) {
|
|
|
- print STDERR "No data returned from HTTP call\n";
|
|
|
|
|
|
|
+ print STDERR "No data returned from RPC call\n";
|
|
|
|
|
+ }
|
|
|
|
|
+ return undef();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Parse into a perl data structure
|
|
|
|
|
+ my $retval = decode_json($data);
|
|
|
|
|
+ if (!defined($retval) || ref($retval) ne 'HASH') {
|
|
|
|
|
+ print STDERR 'Invalid JSON: ' . $data . "\n";
|
|
|
|
|
+ return undef();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Reduce to the result section
|
|
|
|
|
+ if (exists($retval->{'result'})) {
|
|
|
|
|
+ if (ref($retval->{'result'}) eq 'HASH') {
|
|
|
|
|
+ $retval = $retval->{'result'};
|
|
|
|
|
+ } elsif (ref($retval->{'result'}) eq 'ARRAY') {
|
|
|
|
|
+ $retval = @{ $retval->{'result'} }[0];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Return what we've got
|
|
|
|
|
+ if ($JSON_DEBUG) {
|
|
|
|
|
+ print "Result:\n";
|
|
|
|
|
+ printHash($retval, "\t");
|
|
|
|
|
+ }
|
|
|
|
|
+ return $retval;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+sub printHash($$) {
|
|
|
|
|
+ my ($hash, $prefix) = @_;
|
|
|
|
|
+ my $nextPrefix = $prefix . substr($prefix, 0, 1);
|
|
|
|
|
+
|
|
|
|
|
+ if (!defined($hash)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ } elsif (ref($hash) ne 'HASH') {
|
|
|
|
|
+ print STDERR 'Invalid hash: ' . $hash . "\n";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ foreach my $key (keys(%{$hash})) {
|
|
|
|
|
+ if (ref($hash->{$key}) eq 'HASH') {
|
|
|
|
|
+ print STDERR $prefix . $key . ":\n";
|
|
|
|
|
+ printHash($hash->{$key}, $nextPrefix);
|
|
|
|
|
+ } elsif (ref($hash->{$key}) eq 'ARRAY') {
|
|
|
|
|
+ print STDERR $prefix . $key . " => [\n";
|
|
|
|
|
+ foreach my $element (@{ $hash->{$key} }) {
|
|
|
|
|
+ if (ref($element) eq 'HASH') {
|
|
|
|
|
+ printHash($element, $nextPrefix);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ print STDERR $nextPrefix . $element . "\n";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ print STDERR $prefix . "]\n";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ print STDERR $prefix . $key . ' => ' . $hash->{$key} . "\n";
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- $data = '';
|
|
|
|
|
}
|
|
}
|
|
|
- return $data;
|
|
|
|
|
}
|
|
}
|