Difference between revisions of "Perl API examples"

From MythTV Official Wiki
Jump to: navigation, search
m (Writing to the database: expire.pl not .pm)
m (Writing to the database)
Line 260: Line 260:
 
Examples are hard to come by but this code should remove a recording from the database.  Note that the StartTime parameter actually expects StartTS.
 
Examples are hard to come by but this code should remove a recording from the database.  Note that the StartTime parameter actually expects StartTS.
  
{{perl|Expire.pl|
+
{{perl|expire.pl|
 
<pre>
 
<pre>
 
#!/usr/bin/perl -w  
 
#!/usr/bin/perl -w  

Revision as of 20:35, 10 December 2014


Author Phil Brady
Description Interfacing with the Services API interface of Mythtv from a perl script.
Supports Version27.png 



Introduction

This page has been created to give examples of perl code for accessing the new API interface to Mythtv. Note that the API interface is still very new (as at November 2014), is still under development and has certain inconsistencies which still are being ironed out. The documentation is necessarily sparse.

This page is itself very sparse but I hope it will be considered better than nothing and be a springboard for further contributions (and challenges) which are warmly welcomed! The original author does not claim to be a perl expert and the code will certainly be capable of improvement.

Existing sources of information

API wiki pages: Services API

Your own Mythtv server: eg http://192.168.1.67:6544/Channel/wsdl

The 'old' protocol version. Use of this mechanism is discouraged but its pages are a still a good source for (say) program data. Myth Protocol

The mythtv forum has a special API section – fast and helpful responses! https://forum.mythtv.org/

Source code (if desperate!):

https://code.mythtv.org/cgit/mythtv/tree/mythtv/programs/mythbackend/services/dvr.cpp#n146

Reading the Database

Readers will note that many of the API calls will return XML code which consists of

Generic 'header' information (eg Mythtv version) followed by:
Sets of data relating to an individual channel, video source, recording etc.

Code is presented here in module form which makes two routines available to calling perl scripts to reflect each type of data:

GetDBheader which populates a hash with the initial header information in the data returned by API calls.

Calling parameters are:

  • Reference to hash
  • IP of host
  • Name of API
  • Extra parameters to supply to the url (see below).

The hash gets populated with the header information and the return code is the number of entries found.


GetDBinfo which populates a hash with requested fields from the database. Calling parameters are:

  • reference to the hash
  • IP address of host
  • Name of API (eg getrecordedlist). This is case insensitive.
  • Extra information to supply to the url call to the backend (eg 'StartIndex=3&Count=4').
  • Primary key for the database. This must be unique in the database (eg 'FileName' would be unique but not 'ChanId')
  • One or more secondary keys to extract.

If extra parameters is left blank then the routines will normally return data on all recordings, video sources etc. Any initial '?' or '&' should be omitted.

If uniqueness of key is an issue then '#' can be used as a key and an incrementing counter will be returned as the value. The counter will increment at each new recording, channel etc.

The routine populates the hash and returns with the number of hash entries created.

A simple demo program should give a flavour of the routines:

Application-x-perl.png testscan.pl

#!/usr/bin/perl -w
use strict;
use scan_database;


#Example of GetDBheader

print "\nGetDBheader\n";
my %header;
my $resp= GetDBheader(\%header, '192.168.1.67','VideoSourceList');
print "$resp entries returned\n";
for (sort keys %header){
    print "$_  $header{$_}\n";
}


#Example of GetDBinfo -  Note use of # to return a counter.
# If the extra param (StartIndex=2 &Count=10 in this case) is 
# null then all will be returned

print "\nGetDBinfo\n";
my %data;
$resp= GetDBinfo(\%data, '192.168.1.67', 'GetRecordedList', 
      'Startindex=2&Count=10',  'FileName','Title','FileSize','#');

#params are:  reference to hash, host, function, extra info to url call (if any),
#             primary key for hash data, secondary keys.

print "$resp entries returned\n";
print "FileName                  #  FileSize     Title\n";
for (sort keys %data){
    print "$_   $data{$_}{'#'}  $data{$_}{FileSize}   $data{$_}{Title}\n";
}

exit 0;

The above demo needs to be able to access the module code. The module currently only supports ChannelInfoList, GetRecordedList and VideoSourceList but other routines should be straightforward to include (hint: look at the hash  %steer).

The module needs to be put in a file called scan_database.pm with read and execute permissions. It will work if placed either in the current working directory or in the path.

The module

Application-x-perl.png scan_database.pm

package scan_database;

use strict;
use Exporter;
use LWP::UserAgent;  # needs 'sudo apt-get install libwww-perl'

our $VERSION     = 1.00;
our @ISA         = qw(Exporter);
our @EXPORT      = qw(GetDBinfo GetDBheader);
our @EXPORT_OK   = qw($url_buffer);

my $url_buffer;
my $counter;
my $hostopened='';
my $browser;

#Extract data from database
#  Routine GetDBinfo
# params are:
#   reference to hash to fill
#   IP of host
#   Catagory required eg GetRecordedList
#   extra infor to go in call (if any).
#   required hash key
#   list of things to extract
#   eg :-
#   GetDBinfo(\%mydata, '192.168.1.67', 'GetRecordedList', '',
#    'FileName','Title', 'LastModified');
#  If key or extracted name is # then this is replaced by a counter.

#routine GetDBheader
# params are:
#   reference to hash to fill
#   IP of host
#   Catagory required eg GetRecordedList
#   extra info to go in call (if any).
#  eg GetDBheader(\%mydata, '192.168.1.67','getrecordedlist','');



my %steer = (
    getchannelinfolist  => ['Channel/GetChannelInfoList', 'ChannelInfo','ChannelInfos'],
    getrecordedlist     => ['Dvr/GetRecordedList?Descending=true', 'Program','Programs'],
    videosourcelist     => ['Channel/VideoSourceList', 'VideoSource','VideoSources'],
    #add more like this => [url, separator for getDBinfo, separator for getDBheader],  
);
#------------
sub GetDBheader{
    my ($hashref, $ip_addr, $class,$extra)=@_;
    $class=lc $class; 
    unless (defined ($steer{$class})) {die "Sorry - $class not supported"};
    $counter=0;
    #read from backend 
    &ReadBackend($ip_addr, $class, '');

    ($url_buffer)= split /<$steer{$class}[2]>/, $url_buffer;
    my @bits = split /></, $url_buffer.'<';
    foreach (@bits){
        if (m!</!){
            (my $key,$_)=split />/, $_;
            (my $value)=split /</,$_;
            #print "$key = $value\n";
            $$hashref{$key}=$value;
            $counter++;
        }
    }
    return $counter;
}
#-----------------------------
sub ReadBackend{
    my ($ip_addr, $class, $extra)=@_;
    unless ($hostopened eq $ip_addr){
        $browser = LWP::UserAgent->new;
        $browser->timeout(10);
        $hostopened = $ip_addr;
    }
    
    my $command="http://$ip_addr:6544/$steer{$class}[0]";
    if ($extra){
        if ($command =~ /\?/){
            $command .= '&' . $extra;
        }else{
            $command .= '?' . $extra;
        }
    }

    my $response = $browser->get($command);
    unless($response->is_success){ 
        die 'Cannot connect to backend. Bad address?  Not running?'};
    $url_buffer = $response -> content;
}

#---------------------------
sub GetDBinfo{

my ($hashref, $ip_addr, $class, $extra, $key_name, @params)=@_;

    $class=lc $class; 
    unless (defined ($steer{$class})) {die "Sorry - $class not supported"};
 
    #read from backend 
    &ReadBackend($ip_addr, $class, $extra);
    
    $counter=0;
    my $base=index($url_buffer, "<$steer{$class}[1]>");
    while ($base>-1){
        &ProcessRecording($hashref, $base, $key_name, @params);
        $base=index($url_buffer, "<$steer{$class}[1]>", $base+10);
        $counter++;
    }
    return $counter;
}
#---------------
sub ProcessRecording{
    #strip interesting data from record
    my ($hashref, $start_at, $key_name, @params) = @_;

    my $index=$counter;
    if ($key_name ne '#'){ $index=&getparameter($key_name,$start_at)};

    foreach (@params){
        if (/^#$/){
            $$hashref{$index}{$_}=$counter;
        }else{
            $$hashref{$index}{$_}=&getparameter($_, $start_at);
        }
    }
}
#-------------------------
sub getparameter{

my ($param, $base) = @_;

        my $start= index($url_buffer, "<$param>",$base);
        if ($start<0){die "cannot find $param in record"};

        $start=$start + length($param) +2;
        
        my $end=index($url_buffer, "</$param>", $start);
        
        my $data=substr $url_buffer, $start, $end-$start;
        $data =~ s/&/&/g; 
        $data;
}
1;

Writing to the database

Examples are hard to come by but this code should remove a recording from the database. Note that the StartTime parameter actually expects StartTS.


Application-x-perl.png expire.pl

#!/usr/bin/perl -w 
use strict; 
use warnings FATAL => 'uninitialized'; 
use LWP::UserAgent; 

my $StartTS = '2012-01-18T21:26:00Z';	#extracted from GetRecordedList parameter <StartTS> 
my $ChanId = '1002';				# ditto but <ChanId> 

#open the browser 
my $browser = LWP::UserAgent->new; 
$browser->timeout(10); 

#issue post 
my $response = $browser -> post( 
	'http://192.168.1.67:6544/Dvr/RemoveRecorded', 
	[ 
	'StartTime' => $StartTS,          #NB StartTS not StartTime 
	'ChanId'	=> $ChanId, 
	], 
     ); 

unless($response->is_success){die "Error expiring recording"}; 
my $url_buffer = $response -> content; 
unless ($url_buffer =~ /<bool>true/) {print "Bad response expiring\n"}; 
exit 0;

Traps for the unwary

1. Many of the routines have parameters StartIndex and Count. If neither is supplied, then all entries are assumed. Note that there is an inconsistency with StartIndex.

With GetChannelList, StartIndex=0 must be supplied in order to get the first entry. That's logical.

With GetRecordedList, StartIndex=0 and StartIndex=1 return the same data. Thus code which grabs the data in chunks eg

StartIndex=0&Count=100
StartIndex=100&Count=100

will have a duplicate returned because he first call will return 1 to 100 inclusive, the second 100 to 199.

2. GetRecorded and RemoveRecorded both have a 'StartTime' parameter.

Note that the value supplied should be the time a recording started and not the time in the schedules. These will differ in cases where a recording is requested after the start of the recording or if all recordings are started early. If you extract the time from a GetSomethingList call then you need to extract 'StartTS' not 'StartTime'.

More information please!

Have fun and do help to expand this page!