antiram

m3u2hts.py with IPTVSimple.addon tags

Aug 10th, 2015
418
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 16.62 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #===============================================================================
  4. # m3u2hts.py - Generate/update TVHeadend 3.9 channel/tag configuration files from
  5. # IPTV M3U playlist
  6. #
  7. # (c) 2012 Gregor Rudolf, 2015 antiram
  8. # Licensed under the MIT license:
  9. # http://www.opensource.org/licenses/mit-license.php
  10. #===============================================================================
  11. from optparse import OptionParser
  12. import codecs
  13. import re
  14. import os
  15.  
  16. import sys
  17. import shutil
  18. import string
  19.  
  20. try:
  21.     import json
  22. except ImportError:
  23.     # old python? easy_install simplejson
  24.     import simplejson as json
  25.  
  26. PROGNUM = re.compile(r"(\d+) - (.*)")  # #EXTINF:0,1 - SLO 1 -> #1 - num, 2 - ime
  27. URLPART = re.compile(r"^((?P<scheme>.+?)://@?)?(?P<host>.*?)(:(?P<port>\d+?))?$")
  28. #for iptvsimple.addon tags
  29. XMLTV = re.compile('tvg-id="(.*?)"')
  30. GROUP = re.compile('group-title="(.*?)"')
  31. ICON = re.compile('tvg-logo="(.*?)"')
  32. HDTV = re.compile('(.*?)\sHD')
  33.  
  34. #MUXNAME = re.compile('(.+?)"iptv_muxname":\s(.*?)"', re.S)
  35. MUXNAME = re.compile('\{(.*?)\}', re.S)
  36. KEYVALUE = re.compile('\s{0,}"(.*?)":\s"(.*?)"')
  37.  
  38. CHAN_NUMBERING_GENERATE = 0
  39. CHAN_NUMBERING_DURATION = 1
  40. CHAN_NUMBERING_NAMES = 2
  41.  
  42. channels = dict()
  43. tags = dict()
  44. htschannels = dict()
  45. htstags = dict()
  46. htsmuxes = dict()
  47. networkkey = None
  48.  
  49. def readm3u(infile, removenum, channumbering, inputcodec):
  50.     """
  51.    Read IPTV channels from .M3U file
  52.    @param infile: input file
  53.    @param removenum: try to remove channel numbers from names
  54.    @param channumbering: how to get channel number
  55.    @param inputcodec: encoding of input file
  56.    """
  57.  
  58.     instream = codecs.open(infile, "Ur", encoding=inputcodec)
  59.    
  60.     chancnt = 0
  61.     tagcnt = 0
  62.     chname = ''
  63.     chtags = None
  64.     chlanguage = None
  65.     chnumber = None
  66.     chxmltv = None
  67.     chicon = None
  68.     for line in instream.readlines():
  69.         line = line.strip()
  70.         if line.startswith("#EXTINF:"):
  71.             #EXTINF:duration,channel number - channel name
  72.             buff = line[8:].split(',')
  73.             chattr = buff[0].split(' ')
  74.             m = PROGNUM.search(buff[1])
  75.             if removenum and m:
  76.                 chname = m.group(2)
  77.             else:
  78.                 chname = buff[len(buff)-1]
  79.             chname = re.sub(r'\[/?.*?\]', '', chname).strip() #for iptvsimple.addon tags
  80.             if m and channumbering == CHAN_NUMBERING_NAMES:
  81.                 chnumber = m.group(1)
  82.             elif channumbering == CHAN_NUMBERING_DURATION:
  83.                 chnumber = buff[0]
  84.            
  85.             #for tvip.ga
  86.             for i in range(1, len(chattr)):
  87.                 found = False
  88.                 m = XMLTV.match(chattr[i])
  89.                 if m:
  90.                     chxmltv = m.group(1)
  91.                     found = True
  92.                 if not found:
  93.                     m = GROUP.match(chattr[i])
  94.                     if m:
  95.                         chtags = m.group(1).split(',')
  96.                         n = HDTV.match(chname)
  97.                         if n:
  98.                             chtags.append(u'HDTV')
  99.                         else:
  100.                             chtags.append(u'SDTV')
  101.                         for t in chtags:
  102.                             if not t in tags:
  103.                                 tagcnt += 1
  104.                                 tags[t] = {'num': tagcnt, 'name': t}
  105.                         found = True
  106.                 if not found:
  107.                     m = ICON.match(chattr[i])
  108.                     if m:
  109.                         chicon = "file://" + os.getenv("HOME") + "/.icons/" + m.group(1)
  110.             chattr = None
  111.         elif line.startswith('#EXTTV:'):
  112.             #EXTTV:tag[,tag,tag...];language;XMLTV id[;icon URL]
  113.             buff = line[7:].split(';')
  114.             chtags = buff[0].split(',')
  115.             for t in chtags:
  116.                 if not t in tags:
  117.                     tagcnt += 1
  118.                     tags[t] = {'num': tagcnt, 'name': t}
  119.             chlanguage = buff[1]
  120.             if chlanguage:
  121.                 if not chlanguage in tags:
  122.                     tagcnt += 1
  123.                     tags[chlanguage] = {'num': tagcnt, 'name': chlanguage}
  124.                 chtags.append(chlanguage)
  125.             chxmltv = buff[2]
  126.             chicon = buff[3] if len(buff) > 3 else None
  127.         else:
  128.             chgroup = re.search(URLPART, line).groupdict()
  129.             if not chgroup or not chgroup["scheme"]:
  130.                 continue
  131.             chancnt += 1
  132.             if channumbering == CHAN_NUMBERING_GENERATE: chnumber = chancnt
  133.             if chname in channels:
  134.                 print "already exists in m3u: %s" % chname
  135.                 chname += '.'
  136.             channels[chname] = {'num': chancnt, 'number': chnumber, 'name': chname, 'tags': chtags, 'lang': chlanguage,
  137.                                 'scheme': chgroup["scheme"], 'ip': chgroup["host"], 'port': chgroup["port"],
  138.                                 'xmltv': chxmltv, 'icon': chicon}
  139.             chname = ''
  140.             chtags = None
  141.             chlanguage = None
  142.             chnumber = None
  143.             chxmltv = None
  144.             chicon = None
  145.  
  146.  
  147. def uuid():
  148.     import uuid
  149.  
  150.     return uuid.uuid4().hex
  151.  
  152.  
  153. def writechannels39(iface, output, networkname):
  154.     xmltvpath = "epggrab/xmltv/channels"
  155.     if not os.path.exists(xmltvpath):
  156.         os.makedirs(xmltvpath)
  157.  
  158.     tagpath = 'channel/tag'
  159.     if not os.path.exists(tagpath):
  160.         os.makedirs(tagpath)
  161.  
  162.     chnpath = 'channel/config'
  163.     if not os.path.exists(chnpath):
  164.         os.makedirs(chnpath)
  165.  
  166.     #channel/tag/UUID
  167.     for tag in tags.values():
  168.         if tag['name'] not in htstags:
  169.             print "new Tag: %s" % tag['name']
  170.             tag['id'] = uuid()
  171.             jstag = {'enabled': 1,
  172.                      'internal': 0,
  173.                      'titledIcon': 0,
  174.                      'name': tag['name'],
  175.                      'comment': '',
  176.                      'icon': ''}
  177.             writejson(os.path.join(tagpath, tag['id']), jstag)
  178.         else:
  179.             tag['id'] = htstags[tag['name']]['id']
  180.  
  181.     #input/iptv
  182.     path = os.path.join('input', 'iptv')
  183.     if not os.path.exists(path):
  184.         os.makedirs(path)
  185.    
  186.     #input/iptv/config
  187.     if not os.path.isfile(os.path.join(path, 'config')):
  188.         writejson(os.path.join(path, 'config'), {
  189.             'uuid': uuid(),
  190.             'skipinitscan': 1,
  191.             'autodiscovery': 0
  192.         })
  193.    
  194.     #input/iptv/networks/uuid()
  195.     if networkkey is None:
  196.         path = os.path.join(path, 'networks', uuid())
  197.         if not os.path.exists(path):
  198.             os.makedirs(path)
  199.         writejson(os.path.join(path, 'config'), {
  200.             'networkname': networkname,  # Network name
  201.             'skipinitscan': 1,  # Skip initial scan
  202.             'autodiscovery': 0, # Network discovery
  203.             'idlescan': 0,      # Idle scan
  204.             'max_streams': 2,   # Max input streams
  205.             'max_bandwidth': 0, # Max bandwidth (Kbps)
  206.             'max_timeout': 10   # Max timeout (seconds)
  207.         })
  208.         #input/iptv/networks/uuid()/muxes
  209.         path = os.path.join(path, 'muxes')
  210.         if not os.path.exists(path):
  211.             os.mkdir(path)
  212.     else:
  213.         path = os.path.join(path, 'networks', networkkey, 'muxes')
  214.    
  215.     #remove existing channels from dict
  216.     count = len(channels)
  217.     for mkey in htsmuxes.iterkeys():
  218.         if mkey in channels.keys():
  219.             del channels[mkey]
  220.         elif mkey:
  221.             print "%s is new" % mkey        
  222.     print "(%d new Channels)" % len(channels)
  223.    
  224.     #one mux and service for each channel
  225.     for channel in channels.values():
  226.         print "new Channel in m3u: %s" % channel['name']
  227.         muxid = uuid()
  228.         muxpath = os.path.join(path, muxid)
  229.         if not os.path.exists(muxpath):
  230.             os.mkdir(muxpath)
  231.         jsmux = {
  232.             'iptv_url': getUrl(channel),
  233.             'iptv_interface': iface,
  234.             'iptv_atsc': 0,
  235.             'iptv_svcname': channel['name'],
  236.             'iptv_muxname': channel['name'],
  237.             'iptv_sname': channel['name'],
  238.             'enabled': 1,
  239.             'scan_result': 2  # mark scan result (1 - ok, 2 - failed)
  240.         }
  241.         #input/iptv/networks/uuid()/muxes/uuid()/config file
  242.         writejson(os.path.join(muxpath, 'config'), jsmux)
  243.         #input/iptv/networks/uuid()/muxes/uuid()/services/uuid()
  244.         svcpath = os.path.join(muxpath, 'services')
  245.         if not os.path.exists(svcpath):
  246.             os.mkdir(svcpath)
  247.         #create empty service with id 1
  248.         if 'service' in output:
  249.             svcid = uuid()
  250.             jssvc = {
  251.                 'sid': 1,   # guess service id
  252.                 'svcname': channel['name'],
  253.                 'name': channel['name'],
  254.                 'dvb_servicetype': 1,
  255.                 'enabled': 1
  256.             }
  257.             writejson(os.path.join(svcpath, svcid), jssvc)
  258.         else:
  259.             svcid = None
  260.  
  261.         #channel/config
  262.         if 'channel' in output:
  263.             chanid = uuid()
  264.             jschan = {
  265.                 'name': channel['name'],
  266.                 'dvr_pre_time': 0,
  267.                 'dvr_pst_time': 0,
  268.                 'services': [svcid]
  269.             }
  270.             if channel['number'] is not None:
  271.                 jschan['number'] = int(channel['number'])
  272.             if channel['tags'] is not None:
  273.                 jschan['tags'] = list(tags[x]['id'] for x in channel['tags'])
  274.             if channel['icon'] is not None:
  275.                 jschan['icon'] = channel['icon']
  276.             writejson(os.path.join(chnpath, chanid), jschan)
  277.  
  278.             #epg
  279.             #epggrab/xmltv/channels/#
  280.             if channel['xmltv'] is not None:
  281.                 xmlid = channel['xmltv']
  282.                 #else:
  283.                 #    xmlid = channel['name']
  284.                 jsepg = {
  285.                     'name': xmlid,
  286.                     'channels': [chanid],
  287.                     'icon': channel['icon'] #for tvip.ga
  288.                 }
  289.                 #writejson(os.path.join(xmltvpath, chanid), jsepg)
  290.                 if xmlid != '':
  291.                     writejson(os.path.join(xmltvpath, xmlid), jsepg)
  292.  
  293. def getUrl(channel):
  294.     if channel['port']:
  295.         url = 'pipe://ffpipe.sh %s://%s:%s' % (channel['scheme'], channel['ip'], channel['port']) #for tvip.ga
  296.     else:
  297.         url = 'pipe://ffpipe.sh %s %s://%s' % (re.sub(r"\s", "\ ", channel['name']), channel['scheme'], channel['ip']) #for tvip.ga
  298.     return url
  299.  
  300. def readhtsnetworkkey(networkname):
  301.     path = os.path.join(os.getcwd(), 'input', 'iptv', 'networks')
  302.     if os.path.exists(path):
  303.         for nkey in os.listdir(path):
  304.             ifile = codecs.open(os.path.join(path, nkey, 'config'), 'r', 'utf-8')
  305.             for line in ifile.readlines():
  306.                 m = KEYVALUE.match(line)
  307.                 if m:
  308.                     if m.group(1) == 'networkname' and m.group(2) == networkname:
  309.                         return nkey
  310.             ifile.close()
  311.     else:
  312.         return None
  313.  
  314.  
  315. def updatehtsmuxes():
  316.     path = os.path.join(os.getcwd(), 'input', 'iptv', 'networks', networkkey, 'muxes')
  317.     muxname = ''
  318.     url = ''
  319.     for i in os.listdir(path):
  320.         ifile = codecs.open(os.path.join(path, i, 'config'), 'Ur+', 'utf-8')
  321.         for line in ifile.readlines():
  322.             m = KEYVALUE.match(line)
  323.             if m:
  324.                 if m.group(1) == 'iptv_muxname':
  325.                     muxname = m.group(2)
  326.                 elif m.group(1) == 'iptv_url':
  327.                     url = m.group(2)
  328.         if muxname in channels:
  329.             #update mux url
  330.             churl = re.sub(r'\\\s', r'\\\\ ', getUrl(channels[muxname]))
  331.             if url != churl:
  332.                 ifile.seek(0)
  333.                 out = string.replace(ifile.read(), url, churl)
  334.                 ifile.seek(0)
  335.                 ifile.write(out)
  336.                 url = churl
  337.                 print "mux url changed for %s" % muxname
  338.             htsmuxes[muxname] = {'id': i, 'iptv_muxname': muxname, 'iptv_url': url}
  339.         else:
  340.             ifile.close()
  341.             shutil.rmtree(os.path.join(os.getcwd(), 'input', 'iptv', 'networks', networkkey, 'muxes', i))
  342.             print "deleted mux and services for %s" % muxname
  343.         ifile.close()
  344.  
  345.  
  346. def readhtstags():
  347.         path = os.path.join(os.getcwd(), 'channel', 'tag')
  348.         name = ''
  349.         for i in os.listdir(path):
  350.             ifile = codecs.open(os.path.join(path, i), 'Ur', 'utf-8')
  351.             for line in ifile.readlines():
  352.                 m = KEYVALUE.match(line)
  353.                 if m:
  354.                     if m.group(1) == "name":
  355.                         name = m.group(2)
  356.                         htstags[name] = {'id': i, 'name': name}
  357.             ifile.close()
  358.  
  359.  
  360. def updatehtschannels():
  361.         path = os.path.join(os.getcwd(), 'channel', 'config')
  362.         name = ''
  363.         dvr_pst_time = 0
  364.         dvr_pre_time = 0
  365.         number = 0
  366.         icon = ''
  367.         for i in os.listdir(path):
  368.             ifile = codecs.open(os.path.join(path, i), 'Ur+', 'utf-8')
  369.             for line in ifile.readlines():
  370.                 m = KEYVALUE.match(line)
  371.                 if m:
  372.                     if m.group(1) == "name":
  373.                        name = m.group(2)
  374.                     elif m.group(1) == "dvr_pst_time":
  375.                        dvr_pst_time = m.group(2)
  376.                     elif m.group(1) == "dvr_pre_time":
  377.                         dvr_pre_time = m.group(2)
  378.                     elif m.group(1) == "number":
  379.                         number = m.group(2)
  380.                     elif m.group(1) == "icon":
  381.                         icon = m.group(2)
  382.             if name in channels:
  383.                 #print "icon: %s" % channels[name]['icon']
  384.                 if channels[name]['icon'] is not None:
  385.                     chicon = re.sub(r"\\\s", r"\\\\ ", channels[name]['icon'])
  386.                     if icon != chicon:
  387.                         ifile.seek(0)
  388.                         out = string.replace(ifile.read(), icon, chicon)
  389.                         ifile.seek(0)
  390.                         ifile.write(out)
  391.                         icon = chicon
  392.                         print "channel icon changed for %s" % name
  393.             ifile.close()
  394.             htschannels[name] = {'id': i, 'name': name, 'dvr_pst_time': dvr_pst_time, 'dvr_pre_time': dvr_pre_time, 'number': number, 'icon': icon}
  395.         #print "%d hts channels read" % len(htschannels)
  396.  
  397.  
  398. def whatisthis(s):
  399.         if isinstance(s, str):
  400.             print "ordinary string"
  401.         elif isinstance(s, unicode):
  402.             print "unicode string"
  403.         else:
  404.             print "not a string"
  405.  
  406.  
  407. def writejson(filename, obj):
  408.     """
  409.    Export obj to filename in JSON format
  410.    @param filename: output file
  411.    @param obj: object to export
  412.    """
  413.     outstream = codecs.open(filename, "w", encoding='utf-8')
  414.     json.dump(obj, outstream, indent=4, ensure_ascii=False)
  415.     outstream.close()
  416.  
  417.  
  418. def main():
  419.     par = OptionParser(usage="%prog [options] inputfile",
  420.                        description="Generate TVHeadend 3.9 channel/tag configuration files from IPTV M3U playlist")
  421.     par.add_option('-r', '--removenum', action='store_true', help=u'remove program numbers from names')
  422.     par.add_option('-n', '--numbering', type='int', default=0,
  423.                    help=u'program numbers are generated(0), determined from duration(1) or extracted from program names(2)')
  424.     par.add_option('-c', '--codec', action='store', dest='codec', default='cp1250',
  425.                    help=u'input file encoding [default: %default]')
  426.     par.add_option('-i', '--iface', action='store', dest='iface', default='eth1',
  427.                    help=u'IPTV interface [default: %default]')
  428.     par.add_option('-o', '--output', action='append', type='string', dest='output',
  429.                    help=u'what kind of 3.9 output to generate in addition to muxes. '
  430.                         u'Available options are "service" and "channel", repeat to combine [default: none]')
  431.     par.add_option('-l', '--networklabel', action='store', dest='networkname', default='IPTV network',
  432.                     help=u'Network name in tvheadend')
  433.  
  434.     opt, args = par.parse_args()
  435.     if len(args) == 1:
  436.         global networkkey
  437.         networkkey = readhtsnetworkkey(opt.networkname)
  438.         readm3u(args[0], opt.removenum, opt.numbering, opt.codec)
  439.         if networkkey is not None:
  440.             updatehtsmuxes()
  441.             readhtstags()
  442.             updatehtschannels()
  443.         writechannels39(opt.iface, opt.output, opt.networkname)
  444.         print("OK")
  445.     else:
  446.         par.print_help()
  447.  
  448.  
  449. if __name__ == '__main__':
  450.     main()
Add Comment
Please, Sign In to add comment