Ticket #4985: MythTV.diff

File MythTV.diff, 33.9 KB (added by Ian Barton <ian@…>, 12 years ago)
  • mythtv/bindings/python/MythTV/MythTV.py

    old new  
    66The MythTV class to handle connection to and querying of a MythTV backend.
    77The Recorder class representing and storing details of a tuner card.
    88The Program class for storing details of a TV program.
     9The Channel class for storing details of a TV channel.
     10The Guide class for storing details from the program guide.
    911"""
    1012import os
    1113import sys
     
    2123log = MythLog(CRITICAL, '#%(levelname)s - %(message)s', 'MythTV')
    2224
    2325RECSTATUS = {
    24                 'TunerBusy': -8,
    25                 'LowDiskSpace': -7,
    26                 'Cancelled': -6,
    27                 'Deleted': -5,
    28                 'Aborted': -4,
    29                 'Recorded': -3,
    30                 'Recording': -2,
    31                 'WillRecord': -1,
    32                 'Unknown': 0,
    33                 'DontRecord': 1,
    34                 'PreviousRecording': 2,
    35                 'CurrentRecording': 3,
    36                 'EarlierShowing': 4,
    37                 'TooManyRecordings': 5,
    38                 'NotListed': 6,
    39                 'Conflict': 7,
    40                 'LaterShowing': 8,
    41                 'Repeat': 9,
    42                 'Inactive': 10,
    43                 'NeverRecord': 11,
    44                 }
     26                'TunerBusy': -8,
     27                'LowDiskSpace': -7,
     28                'Cancelled': -6,
     29                'Deleted': -5,
     30                'Aborted': -4,
     31                'Recorded': -3,
     32                'Recording': -2,
     33                'WillRecord': -1,
     34                'Unknown': 0,
     35                'DontRecord': 1,
     36                'PreviousRecording': 2,
     37                'CurrentRecording': 3,
     38                'EarlierShowing': 4,
     39                'TooManyRecordings': 5,
     40                'NotListed': 6,
     41                'Conflict': 7,
     42                'LaterShowing': 8,
     43                'Repeat': 9,
     44                'Inactive': 10,
     45                'NeverRecord': 11,
     46                }
    4547
    4648BACKEND_SEP = '[]:[]'
    4749PROTO_VERSION = 40
    4850PROGRAM_FIELDS = 46
     51# CHANNEL_FIELDS = 29
    4952
    5053class MythTV:
    51         """
    52         A connection to a MythTV backend.
    53         """
    54         def __init__(self, conn_type='Monitor'):
    55                 self.db = MythDB(sys.argv[1:])
    56                 self.master_host = self.db.getSetting('MasterServerIP')
    57                 self.master_port = int(self.db.getSetting('MasterServerPort'))
     54        """
     55        A connection to a MythTV backend.
     56        """
     57        def __init__(self, conn_type='Monitor'):
     58                self.db = MythDB(sys.argv[1:])
     59                self.master_host = self.db.getSetting('MasterServerIP')
     60                self.master_port = int(self.db.getSetting('MasterServerPort'))
    5861
    59                 if not self.master_host:
    60                         log.Msg(CRITICAL, 'Unable to find MasterServerIP in database')
    61                         sys.exit(1)
    62                 if not self.master_port:
    63                         log.Msg(CRITICAL, 'Unable to find MasterServerPort in database')
    64                         sys.exit(1)
     62                if not self.master_host:
     63                        log.Msg(CRITICAL, 'Unable to find MasterServerIP in database')
     64                        sys.exit(1)
     65                if not self.master_port:
     66                        log.Msg(CRITICAL, 'Unable to find MasterServerPort in database')
     67                        sys.exit(1)
    6568
    66                 try:
    67                         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    68                         self.socket.settimeout(10)
    69                         self.socket.connect((self.master_host, self.master_port))
    70                         res = self.backendCommand('MYTH_PROTO_VERSION %s' % PROTO_VERSION).split(BACKEND_SEP)
    71                         if res[0] == 'REJECT':
    72                                 log.Msg(CRITICAL, 'Backend has version %s and we speak version %s', res[1], PROTO_VERSION)
    73                                 sys.exit(1)
    74                         res = self.backendCommand('ANN %s %s 0' % (conn_type, socket.gethostname()))
    75                         if res != 'OK':
    76                                 log.Msg(CRITICAL, 'Unexpected answer to ANN command: %s', res)
    77                         else:
    78                                 log.Msg(INFO, 'Successfully connected mythbackend at %s:%d', self.master_host, self.master_port)
    79                 except socket.error, e:
    80                         log.Msg(CRITICAL, 'Couldn\'t connect to %s:%d (is the backend running)', self.master_host, self.master_port)
    81                         sys.exit(1)
     69                try:
     70                        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     71                        self.socket.settimeout(10)
     72                        self.socket.connect((self.master_host, self.master_port))
     73                        res = self.backendCommand('MYTH_PROTO_VERSION %s' % PROTO_VERSION).split(BACKEND_SEP)
     74                        if res[0] == 'REJECT':
     75                                log.Msg(CRITICAL, 'Backend has version %s and we speak version %s', res[1], PROTO_VERSION)
     76                                sys.exit(1)
     77                        res = self.backendCommand('ANN %s %s 0' % (conn_type, socket.gethostname()))
     78                        if res != 'OK':
     79                                log.Msg(CRITICAL, 'Unexpected answer to ANN command: %s', res)
     80                        else:
     81                                log.Msg(INFO, 'Successfully connected mythbackend at %s:%d', self.master_host, self.master_port)
     82                except socket.error, e:
     83                        log.Msg(CRITICAL, 'Couldn\'t connect to %s:%d (is the backend running)', self.master_host, self.master_port)
     84                        sys.exit(1)
    8285
    83         def backendCommand(self, data):
    84                 """
    85                 Sends a formatted command via a socket to the mythbackend.
     86        def backendCommand(self, data):
     87                """
     88                Sends a formatted command via a socket to the mythbackend.
    8689
    87                 Returns the result from the backend.
    88                 """
    89                 def recv():
    90                         """
    91                         Reads the data returned from the backend.
    92                         """
    93                         # The first 8 bytes of the response gives us the length
    94                         data = self.socket.recv(8)
    95                         try:
    96                                 length = int(data)
    97                         except:
    98                                 return ''
    99                         data = []
    100                         while length > 0:
    101                                 chunk = self.socket.recv(length)
    102                                 length = length - len(chunk)
    103                                 data.append(chunk)
    104                         return ''.join(data)
     90                Returns the result from the backend.
     91                """
     92                def recv():
     93                        """
     94                        Reads the data returned from the backend.
     95                        """
     96                        # The first 8 bytes of the response gives us the length
     97                        data = self.socket.recv(8)
     98                        try:
     99                                length = int(data)
     100                        except:
     101                                return ''
     102                        data = []
     103                        while length > 0:
     104                                chunk = self.socket.recv(length)
     105                                length = length - len(chunk)
     106                                data.append(chunk)
     107                        return ''.join(data)
    105108
    106                 command = '%-8d%s' % (len(data), data)
    107                 log.Msg(DEBUG, 'Sending command: %s', command)
    108                 self.socket.send(command)
    109                 return recv()
     109                command = '%-8d%s' % (len(data), data)
     110                log.Msg(DEBUG, 'Sending command: %s', command)
     111                self.socket.send(command)
     112                return recv()
     113# IGB
    110114
    111         def getPendingRecordings(self):
    112                 """
    113                 Returns a list of Program objects which are scheduled to be recorded.
    114                 """
    115                 programs = []
    116                 res = self.backendCommand('QUERY_GETALLPENDING').split(BACKEND_SEP)
    117                 has_conflict = int(res.pop(0))
    118                 num_progs = int(res.pop(0))
    119                 log.Msg(DEBUG, '%s pending recordings', num_progs)
    120                 for i in range(num_progs):
    121                         programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS)
    122                                 + PROGRAM_FIELDS]))
    123                 return programs
     115        def getRecordingList(self, arg):
     116                """Returns a list of recordings as program objects.
     117                   Passing   arg 'Delete' returns the items in
     118                   descending order based on start time.
     119                   Passing arg 'Recording' returns programs currently
     120                   being recorded
     121                   Passing arg 'Play' (or anything else): Returns all items.
     122                   Note the list includes live TV.
     123                """
     124                mythtv = MythTV()
     125                programs = []
     126                res = self.backendCommand('QUERY_RECORDINGS ' + arg).split(BACKEND_SEP)
     127                num_progs = int(res.pop(0))
     128                log.Msg(DEBUG, '%s recordings', num_progs)
     129                for i in range(num_progs):
     130                        programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS)
     131                                + PROGRAM_FIELDS]))
     132                return programs
    124133
    125         def getScheduledRecordings(self):
    126                 """
    127                 Returns a list of Program objects which are scheduled to be recorded.
    128                 """
    129                 programs = []
    130                 res = self.backendCommand('QUERY_GETALLSCHEDULED').split(BACKEND_SEP)
    131                 num_progs = int(res.pop(0))
    132                 log.Msg(DEBUG, '%s scheduled recordings', num_progs)
    133                 for i in range(num_progs):
    134                         programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS)
    135                                 + PROGRAM_FIELDS]))
    136                 return programs
    137134
    138         def getUpcomingRecordings(self):
    139                 """
    140                 Returns a list of Program objects which are scheduled to be recorded.
     135        def getUpcomingRecordings(self):
     136                """Returns a list of upcoming recordings as program objects."""
     137                recordings = ''
     138                mythtv = MythTV()
     139                upcoming =  mythtv.getUpcomingRecordings()
     140                for i in range(len(upcoming)):
     141                        recordings = ('%s%s: %s\n%s\n%s\n\n' % (recordings, recording.title, \
     142            recording.starttime, recording.subtitle, recording.description))
     143                return recordings
    141144
    142                 Sorts the list by recording start time and only returns those with
    143                 record status of WillRecord.
    144                 """
    145                 def sort_programs_by_starttime(x, y):
    146                         if x.starttime > y.starttime:
    147                                 return 1
    148                         elif x.starttime == y.starttime:
    149                                 return 0
    150                         else:
    151                                 return -1
    152                 programs = []
    153                 res = self.getPendingRecordings()
    154                 for p in res:
    155                         if p.recstatus == RECSTATUS['WillRecord']:
    156                                 programs.append(p)
    157                 programs.sort(sort_programs_by_starttime)
    158                 return programs
     145        def getAllScheduledRecordings(self):
     146                """
     147                Returns a list of all scheduled recordings as program objects.
     148                """
     149                mythtv = MythTV()
     150                programs = []
     151                res = self.backendCommand('QUERY_GETALLSCHEDULED').split(BACKEND_SEP)
     152                num_progs = int(res.pop(0))
     153                log.Msg(DEBUG, '%s recordings', num_progs)
     154                for i in range(num_progs):
     155                        programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS)
     156                                + PROGRAM_FIELDS]))
     157                return programs
    159158
    160         def getRecorderList(self):
    161                 """
    162                 Returns a list of recorders, or an empty list if none.
    163                 """
    164                 recorders = []
    165                 c = self.db.cursor()
    166                 c.execute('SELECT cardid FROM capturecard')
    167                 row = c.fetchone()
    168                 while row is not None:
    169                         recorders.append(int(row[0]))
    170                         row = c.fetchone()
    171                 c.close()
    172                 return recorders
     159        def getExpiring(self):
     160                """
     161                Returns a list of recordings about to expire as program objects.
     162                """
     163                mythtv = MythTV()
     164                programs = []
     165                res = self.backendCommand('QUERY_GETEXPIRING').split(BACKEND_SEP)
     166                num_progs = int(res.pop(0))
     167                log.Msg(DEBUG, '%s recordings', num_progs)
     168                for i in range(num_progs):
     169                        programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS)
     170                                + PROGRAM_FIELDS]))
     171                return programs
    173172
    174         def getFreeRecorderList(self):
    175                 """
    176                 Returns a list of free recorders, or an empty list if none.
    177                 """
    178                 res = self.backendCommand('GET_FREE_RECORDER_LIST').split(BACKEND_SEP)
    179                 recorders = [int(d) for d in res]
    180                 return recorders
    181173
    182         def getRecorderDetails(self, recorder_id):
    183                 """
    184                 Returns a Recorder object with details of the recorder.
    185                 """
    186                 c = self.db.cursor()
    187                 c.execute("""SELECT cardid, cardtype, videodevice, hostname
    188                                 FROM capturecard WHERE cardid = %s""", recorder_id)
    189                 row = c.fetchone()
    190                 if row:
    191                         recorder = Recorder(row)
    192                         return recorder
    193                 else:
    194                         return None
     174        def getGuideDataThrough(self):
     175                """
     176                Returns the last date for which guide data is available.
     177                If successful the date is returned in the form 2007-02-03 22:00.
     178                On error 0000-00-00 00:00 is returned.
     179                """
     180                mythtv = MythTV()
     181                res = self.backendCommand('QUERY_GUIDEDATATHROUGH').split(BACKEND_SEP)
    195182
    196         def getCurrentRecording(self, recorder):
    197                 """
    198                 Returns a Program object for the current recorders recording.
    199                 """
    200                 res = self.backendCommand('QUERY_RECORDER %s[]:[]GET_CURRENT_RECORDING' % recorder)
    201                 return Program(res.split(BACKEND_SEP))
     183                return res
    202184
    203         def isRecording(self, recorder):
    204                 """
    205                 Returns a boolean as to whether the given recorder is recording.
    206                 """
    207                 res = self.backendCommand('QUERY_RECORDER %s[]:[]IS_RECORDING' % recorder)
    208                 if res == '1':
    209                         return True
    210                 else:
    211                         return False
     185        def queryUptime(self):
     186                """
     187                Returns the system uptime in seconds. On error returns
     188                Could not determine uptime.
     189                """
     190                mythtv = MythTV()
     191                # Time is returned in seconds or "Could not determine uptime."
     192                # if not available.
     193                res = self.backendCommand('QUERY_UPTIME').split(BACKEND_SEP)
    212194
    213         def isActiveBackend(self, hostname):
    214                 """
    215                 Returns a boolean as to whether the given host is an active backend
    216                 """
    217                 res = self.backendCommand('QUERY_IS_ACTIVE_BACKEND[]:[]%s' % hostname)
    218                 if res == 'TRUE':
    219                         return True
    220                 else:
    221                         return False
     195                return res
     196
     197        def getSetting(self, hostname, setting):
     198                """
     199                Queries the remote host for a specific setting.
     200                The backend will look in the MySQL database table
     201                'settings', and attempt to return the value for the
     202                given setting. It seems only settings with the
     203                hostname set can be retrieved by this call.
     204
     205                On error returns -1.
     206                """
     207                mythtv = MythTV()
     208                res = self.backendCommand('QUERY_SETTING ' + hostname + ' ' + setting).split(BACKEND_SEP)
     209
     210                return res
     211
     212# /IGB
     213
     214        def getPendingRecordings(self):
     215                """
     216                Returns a list of Program objects which are scheduled to be recorded.
     217                """
     218                programs = []
     219                res = self.backendCommand('QUERY_GETALLPENDING').split(BACKEND_SEP)
     220                has_conflict = int(res.pop(0))
     221                num_progs = int(res.pop(0))
     222                log.Msg(DEBUG, '%s pending recordings', num_progs)
     223                for i in range(num_progs):
     224                        programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS)
     225                                + PROGRAM_FIELDS]))
     226                return programs
     227
     228        def getScheduledRecordings(self):
     229                """
     230                Returns a list of Program objects which are scheduled to be recorded.
     231                """
     232                programs = []
     233                res = self.backendCommand('QUERY_GETALLSCHEDULED').split(BACKEND_SEP)
     234                num_progs = int(res.pop(0))
     235                log.Msg(DEBUG, '%s scheduled recordings', num_progs)
     236                for i in range(num_progs):
     237                        programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS)
     238                                + PROGRAM_FIELDS]))
     239                return programs
     240
     241        def getUpcomingRecordings(self):
     242                """
     243                Returns a list of Program objects which are scheduled to be recorded.
     244
     245                Sorts the list by recording start time and only returns those with
     246                record status of WillRecord.
     247                """
     248                def sort_programs_by_starttime(x, y):
     249                        if x.starttime > y.starttime:
     250                                return 1
     251                        elif x.starttime == y.starttime:
     252                                return 0
     253                        else:
     254                                return -1
     255                programs = []
     256                res = self.getPendingRecordings()
     257                for p in res:
     258                        if p.recstatus == RECSTATUS['WillRecord']:
     259                                programs.append(p)
     260                programs.sort(sort_programs_by_starttime)
     261                return programs
     262
     263        def getRecorderList(self):
     264                """
     265                Returns a list of recorders, or an empty list if none.
     266                """
     267                recorders = []
     268                c = self.db.cursor()
     269                c.execute('SELECT cardid FROM capturecard')
     270                row = c.fetchone()
     271                while row is not None:
     272                        recorders.append(int(row[0]))
     273                        row = c.fetchone()
     274                c.close()
     275                return recorders
     276
     277        def getFreeRecorderList(self):
     278                """
     279                Returns a list of free recorders, or an empty list if none.
     280                """
     281                res = self.backendCommand('GET_FREE_RECORDER_LIST').split(BACKEND_SEP)
     282                recorders = [int(d) for d in res]
     283                return recorders
     284
     285        def getRecorderDetails(self, recorder_id):
     286                """
     287                Returns a Recorder object with details of the recorder.
     288                """
     289                c = self.db.cursor()
     290                c.execute("""SELECT cardid, cardtype, videodevice, hostname
     291                                FROM capturecard WHERE cardid = %s""", recorder_id)
     292                row = c.fetchone()
     293                if row:
     294                        recorder = Recorder(row)
     295                        return recorder
     296                else:
     297                        return None
     298
     299        def getCurrentRecording(self, recorder):
     300                """
     301                Returns a Program object for the current recorders recording.
     302                """
     303                res = self.backendCommand('QUERY_RECORDER %s[]:[]GET_CURRENT_RECORDING' % recorder)
     304                return Program(res.split(BACKEND_SEP))
     305
     306        def isRecording(self, recorder):
     307                """
     308                Returns a boolean as to whether the given recorder is recording.
     309                """
     310                res = self.backendCommand('QUERY_RECORDER %s[]:[]IS_RECORDING' % recorder)
     311                if res == '1':
     312                        return True
     313                else:
     314                        return False
     315
     316        def isActiveBackend(self, hostname):
     317                """
     318                Returns a boolean as to whether the given host is an active backend
     319                """
     320                res = self.backendCommand('QUERY_IS_ACTIVE_BACKEND[]:[]%s' % hostname)
     321                if res == 'TRUE':
     322                        return True
     323                else:
     324                        return False
     325        def getChannels(self):
     326                """
     327                Returns a list of channels defined in the database.
     328                """
     329                channels = []
     330                c = self.db.cursor()
     331                c.execute("""SELECT * FROM channel""")
     332                row = c.fetchone()
     333                while row is not None:
     334                        channels.append(Channel(row))
     335                        row = c.fetchone()
     336                c.close()
     337
     338
     339
     340                #while row is not None:
     341                #       channels = Channel(row)
     342                #       row = c.fetchone()
     343                #c.close()
     344                return channels
    222345
     346
     347        def getGuideData(self, chan_id, date):
     348                """
     349                Get guide data for the channel and date specified.
     350                """
     351                # !TODO Error checking on channel id and date values.
     352                guide = []
     353                sql = """SELECT * FROM `program` WHERE (`chanid` = """ + chan_id + """) AND  ('""" + date + """ ' = DATE(`starttime`)) ORDER BY `starttime` ASC"""
     354
     355                c = self.db.cursor()
     356                c.execute(sql)
     357                row = c.fetchone()
     358                while row is not None:
     359                        guide.append(Guide(row))
     360                        row = c.fetchone()
     361                c.close()
     362                return guide
     363
     364
     365class Guide:
     366        """
     367        Represents a single program from the program guide.
     368        """
     369        def __str__(self):
     370                return "%s (%s)" % (self.title, self.starttime.strftime('%Y-%m-%d %H:%M:%S'))
     371
     372        def __repr__(self):
     373                return "%s (%s)" % (self.title, self.starttime.strftime('%Y-%m-%d %H:%M:%S'))
     374
     375        def __init__(self, data):
     376                """
     377                Load data into the object.
     378                """
     379                self.chanid = data[0]
     380                self.starttime  = data[1]
     381                self.endtime  = data[2]
     382                self.title  = data[3]
     383                self.subtitle  = data[4]
     384                self.description  = data[5]
     385                self.category  = data[6]
     386                self.category_type  = data[7]
     387                self.airdate  = data[8]
     388                self.stars  = data[9]
     389                self.previouslyshown  = data[10]
     390                self.title_pronounce  = data[11]
     391                self.stereo  = data[12]
     392                self.subtitled  = data[13]
     393                self.hdtv  = data[14]
     394                self.closecaptioned  = data[15]
     395                self.partnumber  = data[16]
     396                self.parttotal  = data[17]
     397                self.seriesid  = data[18]
     398                self.originalairdate  = data[19]
     399                self.showtype  = data[20]
     400                self.colorcode  = data[21]
     401                self.syndicatedepisodenumber  = data[22]
     402                self.programid = data[23]
     403                self.manualid  = data[24]
     404                self.generic  = data[25]
     405                self.listingsource  = data[26]
     406                self.first  = data[27]
     407                self.last  = data[28]
     408                self.audioprop  = data[29]
     409                self.subtitletypes  = data[30]
     410                self.videoprop  = data[31]
     411
     412class Channel:
     413        """Represents a MythTV channel
     414        """
     415
     416        def __str__(self):
     417                return "%s (%s)" % (self.chanid, self.name)
     418
     419        def __repr__(self):
     420                return "%s (%s)" % (self.chanid, self.name)
     421
     422
     423        def __init__(self, data):
     424                """
     425                Load data into the object.
     426                """
     427                self.chanid = data[0]
     428                self.channum = data[1]
     429                self.freqid = data[2]
     430                self.sourceid = data[3]
     431                self.callsign = data[4]
     432                self.name = data[5]
     433                self.icon = data[6]
     434                self.finetune = data[7]
     435                self.videofilters = data[8]
     436                self.xmltvid = data[9]
     437                self.recpriority = data[10]
     438                self.contrast = data[11]
     439                self.brightness = data[12]
     440                self.colour = data[13]
     441                self.hue = data[14]
     442                self.tvformat = data[15]
     443                self.commfree = data[16]
     444                self.visible = data[17]
     445                self.outputfilters = data[18]
     446                self.useonairguide = data[19]
     447                self.mplexid = data[20]
     448                self.serviceid = data[21]
     449                self.atscsrcid = data[22]
     450                self.tmoffset = data[23]
     451                self.atsc_major_chan = data[24]
     452                self.atsc_minor_chan = data[25]
     453                self.last_record = data[26]
     454                self.default_authority = data[27]
     455                self.commethod = data[28]
     456       
    223457class Recorder:
    224         """
    225         Represents a MythTV capture card.
    226         """
    227         def __str__(self):
    228                 return "Recorder %s (%s)" % (self.cardid, self.cardtype)
     458        """
     459        Represents a MythTV capture card.
     460        """
     461        def __str__(self):
     462                return "Recorder %s (%s)" % (self.cardid, self.cardtype)
    229463
    230         def __repr__(self):
    231                 return "Recorder %s (%s)" % (self.cardid, self.cardtype)
     464        def __repr__(self):
     465                return "Recorder %s (%s)" % (self.cardid, self.cardtype)
    232466
    233         def __init__(self, data):
    234                 """
    235                 Load the list of data into the object.
    236                 """
    237                 self.cardid = data[0]
    238                 self.cardtype = data[1]
    239                 self.videodevice = data[2]
    240                 self.hostname = data[3]
     467        def __init__(self, data):
     468                """
     469                Load the list of data into the object.
     470                """
     471                self.cardid = data[0]
     472                self.cardtype = data[1]
     473                self.videodevice = data[2]
     474                self.hostname = data[3]
    241475
    242476class Program:
    243         """
    244         Represents a program with all the detail known.
    245         """
    246         def __str__(self):
    247                 return "%s (%s)" % (self.title, self.starttime.strftime('%Y-%m-%d %H:%M:%S'))
     477        """
     478        Represents a program with all the detail known.
     479        """
     480        def __str__(self):
     481                return "%s (%s)" % (self.title, self.starttime.strftime('%Y-%m-%d %H:%M:%S'))
    248482
    249         def __repr__(self):
    250                 return "%s (%s)" % (self.title, self.starttime.strftime('%Y-%m-%d %H:%M:%S'))
     483        def __repr__(self):
     484                return "%s (%s)" % (self.title, self.starttime.strftime('%Y-%m-%d %H:%M:%S'))
    251485
    252         def __init__(self, data):
    253                 """
    254                 Load the list of data into the object.
    255                 """
    256                 self.title = data[0]
    257                 self.subtitle = data[1]
    258                 self.description = data[2]
    259                 self.category = data[3]
    260                 try:
    261                         self.chanid = int(data[4])
    262                 except ValueError:
    263                         self.chanid = None
    264                 self.channum = data[5] #chanstr
    265                 self.callsign = data[6] #chansign
    266                 self.channame = data[7]
    267                 self.filename = data[8] #pathname
    268                 self.fs_high = data[9]
    269                 self.fs_low = data[10]
    270                 self.starttime = datetime.fromtimestamp(int(data[11])) # startts
    271                 self.endtime = datetime.fromtimestamp(int(data[12])) #endts
    272                 self.duplicate = int(data[13])
    273                 self.shareable = int(data[14])
    274                 self.findid = int(data[15])
    275                 self.hostname = data[16]
    276                 self.sourceid = int(data[17])
    277                 self.cardid = int(data[18])
    278                 self.inputid = int(data[19])
    279                 self.recpriority = int(data[20])
    280                 self.recstatus = int(data[21])
    281                 self.recordid = int(data[22])
    282                 self.rectype = data[23]
    283                 self.dupin = data[24]
    284                 self.dupmethod = data[25]
    285                 self.recstartts = datetime.fromtimestamp(int(data[26]))
    286                 self.recendts = datetime.fromtimestamp(int(data[27]))
    287                 self.repeat = int(data[28])
    288                 self.programflags = data[29]
    289                 self.recgroup = data[30]
    290                 self.commfree = int(data[31])
    291                 self.outputfilters = data[32]
    292                 self.seriesid = data[33]
    293                 self.programid = data[34]
    294                 self.lastmodified = data[35]
    295                 self.stars = float(data[36])
    296                 self.airdate = data[37]
    297                 self.hasairdate = int(data[38])
    298                 self.playgroup = data[39]
    299                 self.recpriority2 = int(data[40])
    300                 self.parentid = data[41]
    301                 self.storagegroup = data[42]
    302                 self.audio_props = data[43]
    303                 self.video_props = data[44]
    304                 self.subtitle_type = data[45]
     486        def __init__(self, data):
     487                """
     488                Load the list of data into the object.
     489                """
     490                self.title = data[0]
     491                self.subtitle = data[1]
     492                self.description = data[2]
     493                self.category = data[3]
     494                try:
     495                        self.chanid = int(data[4])
     496                except ValueError:
     497                        self.chanid = None
     498                self.channum = data[5] #chanstr
     499                self.callsign = data[6] #chansign
     500                self.channame = data[7]
     501                self.filename = data[8] #pathname
     502                self.fs_high = data[9]
     503                self.fs_low = data[10]
     504                self.starttime = datetime.fromtimestamp(int(data[11])) # startts
     505                self.endtime = datetime.fromtimestamp(int(data[12])) #endts
     506                self.duplicate = int(data[13])
     507                self.shareable = int(data[14])
     508                self.findid = int(data[15])
     509                self.hostname = data[16]
     510                self.sourceid = int(data[17])
     511                self.cardid = int(data[18])
     512                self.inputid = int(data[19])
     513                self.recpriority = int(data[20])
     514                self.recstatus = int(data[21])
     515                self.recordid = int(data[22])
     516                self.rectype = data[23]
     517                self.dupin = data[24]
     518                self.dupmethod = data[25]
     519                self.recstartts = datetime.fromtimestamp(int(data[26]))
     520                self.recendts = datetime.fromtimestamp(int(data[27]))
     521                self.repeat = int(data[28])
     522                self.programflags = data[29]
     523                self.recgroup = data[30]
     524                self.commfree = int(data[31])
     525                self.outputfilters = data[32]
     526                self.seriesid = data[33]
     527                self.programid = data[34]
     528                self.lastmodified = data[35]
     529                self.stars = float(data[36])
     530                self.airdate = data[37]
     531                self.hasairdate = int(data[38])
     532                self.playgroup = data[39]
     533                self.recpriority2 = int(data[40])
     534                self.parentid = data[41]
     535                self.storagegroup = data[42]
     536                self.audio_props = data[43]
     537                self.video_props = data[44]
     538                self.subtitle_type = data[45]
    305539
    306540if __name__ == '__main__':
    307         banner = '\'m\' is a MythTV instance.'
    308         try:
    309                 import readline, rlcompleter
    310         except:
    311                 pass
    312         else:
    313                 readline.parse_and_bind("tab: complete")
    314                 banner = banner + " TAB completion is available."
    315         m = MythTV()
    316         namespace = globals().copy()
    317         namespace.update(locals())
    318         code.InteractiveConsole(namespace).interact(banner)
     541        banner = '\'m\' is a MythTV instance.'
     542        try:
     543                import readline, rlcompleter
     544        except:
     545                pass
     546        else:
     547                readline.parse_and_bind("tab: complete")
     548                banner = banner + " TAB completion is available."
     549        m = MythTV()
     550        namespace = globals().copy()
     551        namespace.update(locals())
     552        code.InteractiveConsole(namespace).interact(banner)
    319553
    320554# vim: ts=4 sw=4: