myth797

cross-eit2.py

Sep 8th, 2021 (edited)
425
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.59 KB | None | 0 0
  1. #!/usr/bin/python3
  2. # -*- coding: utf-8 -*-
  3.  
  4. '''
  5. This script fetches EPG data for specified channels from a mythtv database
  6. and exports this data to an XMLTV [1] formatted XML file.
  7.    
  8. The purpose of this is to extract EPG from channels with good data,
  9. and use this data to populate other channels where no data is available.
  10.  
  11. Script usage:
  12.    cross-eit2.py OPTIONS chanid1,xmlid1 chanid2,xmlid2 chanidN,xmlidN
  13.  
  14. Options:
  15.    -o, --output=file   Write output to XML file.
  16.                        If this argument is not specified output is written to ./output.xml
  17.    -h, --help          Show help text (this text)
  18.  
  19. To check the channel numbers for the channels you want to extract EPG from, run the following command:
  20. mysql -u mythtv -p mythconverg -e \"SELECT chanid,callsign from channel WHERE callsign='channel name'\"
  21.  
  22. If you want to import the EPG data from the xml file using mythfilldatabase,
  23. you must add the appropriate xmltvid for each channel you want to import EPG to in mythweb [2]
  24. as well as uncheck the useonairguide for these channels.
  25.  
  26. Example: (assuming sourceid of 1 for mythtv)
  27.    ./cross-eit.py 1004,tv2.guide 1108,history.guide 1022,natgeo.guide -o /tmp/export.xml
  28.    mythfilldatabase -v xmltv  --file 1 /tmp/export.xml
  29.  
  30. [1] http://wiki.xmltv.org/index.php/XMLTVFormat
  31. [2] http://localhost/mythweb/settings/tv/channels
  32. '''
  33.  
  34.  
  35.  
  36. from __future__ import print_function
  37. from datetime import datetime, timedelta
  38. import argparse
  39. import logging
  40. import re
  41. import sys
  42. import time
  43. from MythTV.services_api import send as api
  44. from MythTV.services_api import utilities as util
  45. from xml.etree.ElementTree import Element, SubElement, tostring
  46.  
  47. DAY_IN_SECONDS = 24 * 60 * 60
  48. TIME_NOW = time.mktime(datetime.now().timetuple())
  49. WHITE = '\033[0m'
  50. YELLOW = '\033[93m'
  51.  
  52.  
  53. def flags_to_strings(flgs):
  54.     '''
  55.    Convert the decimal flags to a printable string. From:
  56.    libs/libmyth/programtypes.h. The </> print for values that
  57.    haven't been defined here yet (and maybe should be.)
  58.    '''
  59.     strlst = list()
  60.     if flgs & (0x00fff):
  61.         strlst.append('<')
  62.     if flgs & (1 << 12):
  63.         strlst.append('Rerun')
  64.     if flgs & (1 << 13):
  65.         strlst.append('Dup')
  66.     if flgs & (1 << 14):
  67.         strlst.append('React')
  68.     if flgs & (0xf8000):
  69.         strlst.append('>')
  70.     return ', '.join(strlst)
  71.  
  72.  
  73. def process_command_line():
  74.  
  75.     parser = argparse.ArgumentParser(description='Fetch EPG data for specified channels from a mythtv database and export this data to an XMLTV formatted XML file',
  76.                                      epilog='Default values are in ()s')
  77.  
  78.     parser.add_argument('--debug', action='store_true',
  79.                         help='turn on debug messages (%(default)s)')
  80.  
  81.     parser.add_argument('--digest', type=str, metavar='<user:pass>',
  82.                         help='digest username:password')
  83.  
  84.     parser.add_argument('--forcehd', action='store_true', default=False,
  85.                         help='forces all output programmes to be flagged as HD (%(default)s)')
  86.  
  87.     parser.add_argument('--host', type=str, required=False,
  88.                         default='localhost',
  89.                         metavar='<hostname>',
  90.                         help='backend hostname (%(default)s)')
  91.  
  92.     parser.add_argument('--output', type=str, default='./output.xml', metavar='<file>',
  93.                         help='output XML file name (%(default)s)')
  94.  
  95.     parser.add_argument('--port', type=int, default=6544, metavar='<port>',
  96.                         help='port number of the Services API (%(default)s)')
  97.  
  98.     parser.add_argument('--shift', type=int, default=0, metavar='<time shift>',
  99.                         help='time in minutes to move programme data (%(default)s)')
  100.  
  101.     parser.add_argument('--version', action='version', version='%(prog)s 0.10')
  102.  
  103.     parser.add_argument('channels', metavar='chanidN,xmlidN', nargs="+", type=str,
  104.                         help='list of chanid & xmlid pairs e.g. chanid1,xmlid1 chanid2,xmlid2 chanidN,xmlidN')
  105.  
  106.  
  107.     return parser.parse_args()
  108.  
  109. def d2s(dateobj, tshift):
  110.     """
  111.    Reads datetime string from services api and returns a string in XLMTV format with timezone & time shift offsets.  
  112.    """
  113.     date = datetime.strptime(dateobj, "%Y-%m-%dT%H:%M:%SZ")
  114.     tshift = -tshift
  115.     #tshift = tshift + time.altzone//60 ---> no need to adjust for local timezone?
  116.     return date.strftime("%Y%m%d%H%M%S"+ ' ' + "{:+03d}".format(tshift//60) + "{:02d}".format(tshift%60))
  117.  
  118. def writexmltv(elem, file):
  119.     f = open(file,'w')
  120.     print ("Writing XMLTV EPG information to file %s\n" % file)
  121.     f.write('<?xml version="1.0" ?>\n')
  122.     f.write('<!DOCTYPE tv SYSTEM "xmltv.dtd">\n')
  123.     #write tv XML element including all program subelements
  124.     f.write(tostring(elem, encoding="unicode"))
  125.     f.close()
  126.  
  127.    
  128. def main():
  129.     '''
  130.    Get the channel program info from the backend and process it
  131.    '''
  132.  
  133.     args = process_command_line()    
  134.     opts = dict()
  135.  
  136.     logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
  137.     logging.getLogger('requests.packages.urllib3').setLevel(logging.WARNING)
  138.     logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)
  139.  
  140.     channels={}
  141.     for chan in args.channels:
  142.         try:
  143.             c,x=chan.split(",")
  144.         except:
  145.             print ("Error: Not able to parse channel %s. Aborting...\n" % chan)
  146.             usage()
  147.             sys.exit(2)      
  148.         try:
  149.             testint=int(c)
  150.         except:
  151.             print ("Error: Channel number is not an integer. Aborting...\n")
  152.             usage()
  153.             sys.exit(2)
  154.         if len(x) == 0:
  155.             print ("Error: XMLTVID is not specified for channel %s. Aborting...\n" % (c))
  156.             usage()
  157.             sys.exit(2)
  158.         print (c,x)
  159.         channels[int(c)]=x
  160.  
  161.     try:
  162.         opts['user'], opts['pass'] = args.digest.split(':')
  163.     except (AttributeError, ValueError):
  164.         pass
  165.  
  166.     #################################################################
  167.     #  Request the program listing from the backend and check the   #
  168.     #  response ...                                                 #
  169.     #################################################################
  170.  
  171.     endpoint = 'Guide/GetProgramList'
  172.  
  173.     backend = api.Send(host=args.host)
  174.  
  175.     util.get_utc_offset(backend=backend, opts=opts) # not needed?
  176.  
  177.     #create xml object "tv"
  178.     tv = Element('tv')
  179.    
  180.     for chan in channels:
  181.         rest = 'chanid=' + str(chan) + '&Details=true'
  182.         try:
  183.             resp_dict = backend.send(endpoint=endpoint, rest=rest, opts=opts)
  184.         except RuntimeError as error:
  185.             sys.exit('\nFatal error: "{}"'.format(error))
  186.  
  187.        
  188.         count = int(resp_dict['ProgramList']['TotalAvailable'])
  189.         programs = resp_dict['ProgramList']['Programs']
  190.  
  191.         if args.debug:
  192.             print('Debug: Count of found programmes= {}'.format(count))
  193.  
  194.         if count < 1:
  195.             print('\nNo matching 'visible' channels found.\n')
  196.             sys.exit(0)
  197.  
  198.         #Read schedule for each channel and create XML elements
  199.        
  200.         for program in programs:
  201.             tvprogram         = SubElement(tv, 'programme', channel=channels[chan],
  202.                                            start=d2s(program['StartTime'],args.shift),
  203.                                            stop=d2s(program['EndTime'],args.shift))
  204.             tvtitle           = SubElement(tvprogram, 'title')
  205.             tvtitle.text      = program['Title']
  206.             tvsubtitle        = SubElement(tvprogram, 'sub-title')
  207.             tvsubtitle.text   = program['SubTitle']
  208.             tvdesc            = SubElement(tvprogram, 'desc')
  209.             tvdesc.text       = program['Description']
  210.             tvcategory        = SubElement(tvprogram, 'category')
  211.             tvcategory.text   = program['Category']
  212.             if program['Repeat']=='true':
  213.                 tvprevshown       = SubElement(tvprogram, 'previously-shown')
  214.                 tvprevshown.text  = ' /'
  215.             tvairdate         = SubElement(tvprogram, 'date')
  216.             tvairdate.text    = program['Airdate']
  217.             tvepnum           = SubElement(tvprogram, 'episode-num')
  218.             tvepnum.text      = program['Episode']
  219.             if args.forcehd:
  220.                 tvvideo           = SubElement(tvprogram, 'video')
  221.                 tvquality         = SubElement(tvvideo, 'quality')
  222.                 tvquality.text    = 'HDTV'
  223.  
  224.     writexmltv(tv, args.output)
  225.  
  226.  
  227.  
  228. if __name__ == '__main__':
  229.     main()
  230.  
  231. # vim: set expandtab tabstop=4 shiftwidth=4 smartindent colorcolumn=80:
  232.  
Add Comment
Please, Sign In to add comment