Просмотр исходного кода

Complete the JSON status parser

Zachary Kotlarek 12 лет назад
Родитель
Сommit
ca5ccd548f
1 измененных файлов с 198 добавлено и 40 удалено
  1. 198 40
      plexMonitor/plexMonitor-json.pl

+ 198 - 40
plexMonitor/plexMonitor-json.pl

@@ -6,26 +6,28 @@ use File::Basename;
 use Time::HiRes qw( sleep );
 use File::Temp qw( tempfile );
 use URI::Escape;
+use JSON;
 
 # Prototypes
-sub xbmcHTTP($);
+sub buildCmd($$);
+sub xbmcJSON($$);
+sub printHash($$);
 
 # App config
+my $BASE_URL = 'http://localhost:3005/jsonrpc?request=';
 my $TEMP_DIR = `getconf DARWIN_USER_TEMP_DIR`;
 chomp($TEMP_DIR);
 my $DATA_DIR = $TEMP_DIR . '/plexMonitor';
 
-# xbmcHTTP commands
-my %CMDS = (
-	'PLAYING' => 'GetCurrentlyPlaying',
-	'GUI'     => 'GetGuiStatus'
-);
-
 # Debug
 my $DEBUG = 0;
 if ($ENV{'DEBUG'}) {
 	$DEBUG = 1;
 }
+my $JSON_DEBUG = 0;
+if ($ENV{'JSON_DEBUG'}) {
+	$JSON_DEBUG = 1;
+}
 
 # Command-line arguments
 my ($DELAY) = @ARGV;
@@ -38,35 +40,114 @@ if (!-d $TEMP_DIR) {
 	die("Bad config\n");
 }
 
-# Mode
-my $MODE = 'PLAYING';
-if (basename($0) =~ /GUI/i) {
-	$MODE = 'GUI';
-}
-
 # Add the data directory as needed
 if (!-d $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)
-my $data     = '';
-my $dataLast = '';
+my %data     = ();
+my %dataLast = ();
 do {
 
-	# Run the monitor command
+	# Only update the file on a change
 	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
-	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 ($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);
-		print $fh $data;
+		print $fh $str;
 		close($fh);
 		rename($tmp, $OUT_FILE);
 	}
@@ -90,26 +179,95 @@ do {
 # Exit cleanly
 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
-	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 ($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;
 }