From 1d8e30310170eaad3c64fda4b452adfa043b5886 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Wed, 5 Dec 2018 20:15:10 -0500 Subject: [PATCH] 2018-12-05 Fred Gleason * Added a set of Python classes for processing PAD updates. --- ChangeLog | 2 + apis/Makefile.am | 3 +- apis/PyPAD/Makefile.am | 39 ++++++ apis/PyPAD/api/Makefile.am | 40 ++++++ apis/PyPAD/api/PyPAD.py | 198 ++++++++++++++++++++++++++++ apis/PyPAD/api/__init__.py | 0 apis/PyPAD/examples/Makefile.am | 38 ++++++ apis/PyPAD/examples/now_and_next.py | 18 +++ configure.ac | 8 ++ lib/rdconf.cpp | 10 +- lib/rdlogplay.cpp | 2 +- lib/rdweb.cpp | 2 +- 12 files changed, 356 insertions(+), 4 deletions(-) create mode 100644 apis/PyPAD/Makefile.am create mode 100644 apis/PyPAD/api/Makefile.am create mode 100644 apis/PyPAD/api/PyPAD.py create mode 100644 apis/PyPAD/api/__init__.py create mode 100644 apis/PyPAD/examples/Makefile.am create mode 100755 apis/PyPAD/examples/now_and_next.py diff --git a/ChangeLog b/ChangeLog index 6f22c37e..f328481e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18100,3 +18100,5 @@ 2018-12-05 Fred Gleason * Added a set of enclosing '{}' braces around the JSON-formatted PAD output to make it well-formed. +2018-12-05 Fred Gleason + * Added a set of Python classes for processing PAD updates. diff --git a/apis/Makefile.am b/apis/Makefile.am index 231f9775..320f61dd 100644 --- a/apis/Makefile.am +++ b/apis/Makefile.am @@ -20,7 +20,8 @@ ## ## Use automake to process this into a Makefile.in -SUBDIRS = rivwebcapi\ +SUBDIRS = PyPAD\ + rivwebcapi\ rlm CLEANFILES = *~\ diff --git a/apis/PyPAD/Makefile.am b/apis/PyPAD/Makefile.am new file mode 100644 index 00000000..a2c95b53 --- /dev/null +++ b/apis/PyPAD/Makefile.am @@ -0,0 +1,39 @@ +## automake.am +## +## Automake.am for Rivendell PyPAD +## +## (C) Copyright 2018 Fred Gleason +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as +## published by the Free Software Foundation; either version 2 of +## the License, or (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public +## License along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## +## Use automake to process this into a Makefile.in + +SUBDIRS = api\ + examples + +CLEANFILES = *~\ + *.idb\ + *ilk\ + *.obj\ + *.pdb\ + *.qm\ + moc_* + +MAINTAINERCLEANFILES = *~\ + *.tar.gz\ + aclocal.m4\ + configure\ + Makefile.in\ + moc_* diff --git a/apis/PyPAD/api/Makefile.am b/apis/PyPAD/api/Makefile.am new file mode 100644 index 00000000..a3e3ced0 --- /dev/null +++ b/apis/PyPAD/api/Makefile.am @@ -0,0 +1,40 @@ +## automake.am +## +## Automake.am for Rivendell PyPAD/api +## +## (C) Copyright 2018 Fred Gleason +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as +## published by the Free Software Foundation; either version 2 of +## the License, or (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public +## License along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## +## Use automake to process this into a Makefile.in + +rivendelldir = $(pkgpyexecdir) +rivendell_PYTHON = __init__.py\ + PyPAD.py + +CLEANFILES = *~\ + *.idb\ + *ilk\ + *.obj\ + *.pdb\ + *.qm\ + moc_* + +MAINTAINERCLEANFILES = *~\ + *.tar.gz\ + aclocal.m4\ + configure\ + Makefile.in\ + moc_* diff --git a/apis/PyPAD/api/PyPAD.py b/apis/PyPAD/api/PyPAD.py new file mode 100644 index 00000000..75be895e --- /dev/null +++ b/apis/PyPAD/api/PyPAD.py @@ -0,0 +1,198 @@ +#!/usr/bin/python + +# PyPAD.py +# +# PAD processor for Rivendell +# +# (C) Copyright 2018 Fred Gleason +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import socket +import json + +class PyPADUpdate(object): + def __init__(self,pad_data): + self.__fields=pad_data; + + def __replace(self,wildcard,string): + stype='now' + if wildcard[1].isupper(): + stype='next' + sfields={'a':'artist','b':'label','c':'client','d':'','e':'agency', + 'f':'','g':'groupName','h':'length','i':'description', + 'j':'cutNumber','k':'','l':'album','m': 'composer', + 'n':'cartNumber','o':'outcue','p':'publisher','q': '', + 'r':'conductor','s':'songId','t':'title','u':'userDefined', + 'v':'','w':'','x':'','y':'year', + 'z':''} + sfield=sfields[wildcard[1].lower()] + + try: + string=string.replace(wildcard,self.__fields['padUpdate'][stype][sfield]) + except TypeError: + string=string.replace(wildcard,'') + except KeyError: + string=string.replace(wildcard,'') + return string + + def dateTime(self): + """ + Returns the date-time stamp of the update in RFC-822 format (string). + """ + return self.__fields['padUpdate']['dateTime'] + + def logMachine(self): + """ + Returns the log machine number to which this update pertains + (integer). + """ + return self.__fields['padUpdate']['logMachine'] + + def onairFlag(self): + """ + Returns the state of the on-air flag (boolean). + """ + return self.__fields['padUpdate']['onairFlag'] + + def hasService(self): + """ + Indicates if service information is included with this update + (boolean). + """ + try: + return self.__fields['padUpdate']['service']!=None + except TypeError: + return False; + + def serviceName(self): + """ + Returns the name of the service associated with this update (string). + """ + return self.__fields['padUpdate']['service']['name'] + + def serviceDescription(self): + """ + Returns the description of the service associated with this update + (string). + """ + return self.__fields['padUpdate']['service']['description'] + + def serviceProgramCode(self): + """ + Returns the Program Code of the service associated with this update + (string). + """ + return self.__fields['padUpdate']['service']['programCode'] + + def hasLog(self): + """ + Indicates if log information is included with this update + (boolean). + """ + try: + return self.__fields['padUpdate']['log']!=None + except TypeError: + return False; + + def logName(self): + """ + Returns the name of the log associated with this update (string). + """ + return self.__fields['padUpdate']['log']['name'] + + def padFields(self,string): + """ + Takes an argument of a string containing one or more PAD wildcards, + which it will resolve into the appropriate values. See the + 'Metadata Wildcards' section of the Rivendell Operations Guide + for a list of recognized wildcards. + """ + for i in range(65,68): + string=self.__replace('%'+chr(i),string) + for i in range(69,91): + string=self.__replace('%'+chr(i),string) + for i in range(97,100): + string=self.__replace('%'+chr(i),string) + for i in range(101,123): + string=self.__replace('%'+chr(i),string) + return string + + def hasNowPad(self): + """ + Indicates if this update include 'Now' playing PAD. + """ + try: + return self.__fields['padUpdate']['now']!=None + except TypeError: + return False; + + def hasNextPad(self): + """ + Indicates if this update include 'Next' playing PAD. + """ + try: + return self.__fields['padUpdate']['next']!=None + except TypeError: + return False; + + +class PyPADReceiver(object): + def __init__(self): + self.__callback=None + self.__priv=None + + def __PyPAD_Process(self,pad): + self.__callback(pad,self.__priv) + + def setCallback(self,cb): + """ + Set the processing callback. + """ + self.__callback=cb + + def setPrivateObject(self,obj): + """ + Set the 'private' object. This object will be passed to the + callback in its second argument. + """ + self.priv=obj + + def start(self,hostname): + """ + Connect to a Rivendell system and begin processing PAD events. + Once started, a PyPAD object can be interacted with + only within one of its callback methods. + Takes the following argument: + + hostname: The hostname or IP address of the Rivendell system. + """ + sock=socket.socket(socket.AF_INET) + conn=sock.connect((hostname,34289)) + c="" + line="" + msg="" + + while 1<2: + c=sock.recv(1) + line+=c + if c[0]=="\n": + msg+=line + if line=="\r\n": + self.__PyPAD_Process(PyPADUpdate(json.loads(msg))) + msg="" + line="" + + diff --git a/apis/PyPAD/api/__init__.py b/apis/PyPAD/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apis/PyPAD/examples/Makefile.am b/apis/PyPAD/examples/Makefile.am new file mode 100644 index 00000000..2c62dab5 --- /dev/null +++ b/apis/PyPAD/examples/Makefile.am @@ -0,0 +1,38 @@ +## automake.am +## +## Automake.am for Rivendell PyPAD/examples +## +## (C) Copyright 2018 Fred Gleason +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as +## published by the Free Software Foundation; either version 2 of +## the License, or (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public +## License along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## +## Use automake to process this into a Makefile.in + +EXTRA_DIST = now_and_next.py + +CLEANFILES = *~\ + *.idb\ + *ilk\ + *.obj\ + *.pdb\ + *.qm\ + moc_* + +MAINTAINERCLEANFILES = *~\ + *.tar.gz\ + aclocal.m4\ + configure\ + Makefile.in\ + moc_* diff --git a/apis/PyPAD/examples/now_and_next.py b/apis/PyPAD/examples/now_and_next.py new file mode 100755 index 00000000..dca86d35 --- /dev/null +++ b/apis/PyPAD/examples/now_and_next.py @@ -0,0 +1,18 @@ +#!/usr/bin/python + +import rivendell.PyPAD + +def ProcessPad(update,priv): + print + if update.hasNowPad(): + print update.padFields("NOW: %a - %t") + else: + print "NOW: [none]" + if update.hasNextPad(): + print update.padFields("NEXT: %A - %T") + else: + print "NEXT: [none]" + +rcvr=rivendell.PyPAD.PyPADReceiver() +rcvr.setCallback(ProcessPad) +rcvr.start("localhost") diff --git a/configure.ac b/configure.ac index 456724ff..4e7273fe 100644 --- a/configure.ac +++ b/configure.ac @@ -247,6 +247,11 @@ AC_CHECK_HEADER(security/pam_appl.h,[],[AC_MSG_ERROR([*** PAM not found ***])]) # AC_CHECK_HEADER(soundtouch/SoundTouch.h,[],[AC_MSG_ERROR([*** SoundTouch not found ***])]) +# +# Check for Python +# +AM_PATH_PYTHON([2.7]) + # # Check for FLAC # @@ -462,6 +467,9 @@ AC_CONFIG_FILES([rivendell.spec \ icons/Makefile \ helpers/Makefile \ apis/Makefile \ + apis/PyPAD/Makefile \ + apis/PyPAD/api/Makefile \ + apis/PyPAD/examples/Makefile \ apis/rivwebcapi/Makefile \ apis/rivwebcapi/rivwebcapi.pc \ apis/rivwebcapi/rivwebcapi/Makefile \ diff --git a/lib/rdconf.cpp b/lib/rdconf.cpp index 0cd28dbd..08e16bb5 100644 --- a/lib/rdconf.cpp +++ b/lib/rdconf.cpp @@ -991,7 +991,15 @@ int RDTimeZoneOffset() tm=gmtime(&t); time_t gmt_time=3600*tm->tm_hour+60*tm->tm_min+tm->tm_sec; - return gmt_time-local_time; + int offset=gmt_time-local_time; + if(offset>43200) { + offset=offset-86400; + } + if(offset<-43200) { + offset=offset+86400; + } + + return offset; } diff --git a/lib/rdlogplay.cpp b/lib/rdlogplay.cpp index 6259de4e..2e2b84f1 100644 --- a/lib/rdlogplay.cpp +++ b/lib/rdlogplay.cpp @@ -2964,7 +2964,7 @@ void RDLogPlay::SendNowNext() play_pad_socket->write(QString("{\r\n").toUtf8()); play_pad_socket->write(QString(" \"padUpdate\": {\r\n").toUtf8()); play_pad_socket->write(RDJsonField("dateTime",QDateTime::currentDateTime(),8).toUtf8()); - play_pad_socket->write(RDJsonField("logMachine",play_id,8)); + play_pad_socket->write(RDJsonField("logMachine",play_id+1,8)); play_pad_socket->write(RDJsonField("onairFlag",play_onair_flag,8)); play_pad_socket->write(RDJsonField("logMode",RDAirPlayConf::logModeText(play_op_mode),8)); diff --git a/lib/rdweb.cpp b/lib/rdweb.cpp index c255fb83..bcc991d1 100644 --- a/lib/rdweb.cpp +++ b/lib/rdweb.cpp @@ -1213,7 +1213,7 @@ QString RDWebDateTime(const QDateTime &datetime) tzstr="GMT"; } - return RDLocalToUtc(datetime).toString("ddd, dd MMM yyyy hh:mm:ss")+" "+tzstr; + return datetime.toString("ddd, dd MMM yyyy hh:mm:ss")+" "+tzstr; }