Guest User

Untitled

a guest
Nov 8th, 2018
197
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 62.31 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3.  
  4. """
  5. e2m3u2bouquet.e2m3u2bouquet -- Enigma2 IPTV m3u to bouquet parser
  6.  
  7. @author: Dave Sully, Doug Mackay
  8. @copyright: 2017 All rights reserved.
  9. @license: GNU GENERAL PUBLIC LICENSE version 3
  10. @deffield updated: Updated
  11. """
  12.  
  13. import sys
  14. import os
  15. import re
  16. import unicodedata
  17. import datetime
  18. import urllib
  19. import imghdr
  20. import tempfile
  21. import glob
  22. import ssl
  23. import hashlib
  24. import base64
  25. from PIL import Image
  26. from collections import OrderedDict
  27. from collections import deque
  28. from xml.etree import ElementTree
  29. from argparse import ArgumentParser
  30. from argparse import RawDescriptionHelpFormatter
  31. import e2m3u2bouquet
  32.  
  33.  
  34. __all__ = []
  35. __version__ = '0.7.0'
  36. __date__ = '2017-06-04'
  37. __updated__ = '2017-11-11'
  38.  
  39. DEBUG = 0
  40. TESTRUN = 0
  41.  
  42. ENIGMAPATH = '/etc/enigma2/'
  43. EPGIMPORTPATH = '/etc/epgimport/'
  44. CFGPATH = os.path.join(ENIGMAPATH, 'e2m3u2bouquet/')
  45. PICONSPATH = '/usr/share/enigma2/picon/'
  46. PROVIDERS = {}
  47. PROVIDERSURL = 'https://raw.githubusercontent.com/su1s/e2m3u2bouquet/master/providers.enc'
  48.  
  49. class CLIError(Exception):
  50. """Generic exception to raise and log different fatal errors."""
  51. def __init__(self, msg):
  52. super(CLIError).__init__(type(self))
  53. self.msg = "E: %s" % msg
  54.  
  55. def __str__(self):
  56. return self.msg
  57.  
  58. def __unicode__(self):
  59. return self.msg
  60.  
  61. class AppUrlOpener(urllib.FancyURLopener):
  62. """Set user agent for downloads"""
  63. version = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'
  64.  
  65. class IPTVSetup:
  66. def __init__(self):
  67. # welcome message
  68. print('\n********************************')
  69. print('Starting Engima2 IPTV bouquets v{}'.format(__version__))
  70. print(str(datetime.datetime.now()))
  71. print("********************************\n")
  72.  
  73. def uninstaller(self):
  74. """Clean up routine to remove any previously made changes"""
  75. print('----Running uninstall----')
  76. try:
  77. # Bouquets
  78. print('Removing old IPTV bouquets...')
  79. for fname in os.listdir(ENIGMAPATH):
  80. if 'userbouquet.suls_iptv_' in fname:
  81. os.remove(os.path.join(ENIGMAPATH, fname))
  82. elif 'bouquets.tv.bak' in fname:
  83. os.remove(os.path.join(ENIGMAPATH, fname))
  84. # Custom Channels and sources
  85. print('Removing IPTV custom channels...')
  86. if os.path.isdir(EPGIMPORTPATH):
  87. for fname in os.listdir(EPGIMPORTPATH):
  88. if 'suls_iptv_' in fname:
  89. os.remove(os.path.join(EPGIMPORTPATH, fname))
  90. # bouquets.tv
  91. print('Removing IPTV bouquets from bouquets.tv...')
  92. os.rename(os.path.join(ENIGMAPATH, 'bouquets.tv'), os.path.join(ENIGMAPATH, 'bouquets.tv.bak'))
  93. tvfile = open(os.path.join(ENIGMAPATH, 'bouquets.tv'), 'w+')
  94. bakfile = open(os.path.join(ENIGMAPATH, 'bouquets.tv.bak'))
  95. for line in bakfile:
  96. if '.suls_iptv_' not in line:
  97. tvfile.write(line)
  98. bakfile.close()
  99. tvfile.close()
  100. except Exception, e:
  101. raise e
  102. print('----Uninstall complete----')
  103.  
  104. def download_m3u(self, url):
  105. """Download m3u file from url"""
  106. path = tempfile.gettempdir()
  107. filename = os.path.join(path, 'e2m3u2bouquet.m3u')
  108. print("\n----Downloading m3u file----")
  109. if DEBUG:
  110. print("m3uurl = {}".format(url))
  111. try:
  112. urllib.urlretrieve(url, filename)
  113. except Exception, e:
  114. raise e
  115. return filename
  116.  
  117. def download_providers(self, url):
  118. """Download providers file from url"""
  119. path = tempfile.gettempdir()
  120. filename = os.path.join(path, 'providers.txt')
  121. print("\n----Downloading providers file----")
  122. if DEBUG:
  123. print("providers url = {}".format(url))
  124. try:
  125. # context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
  126. context = ssl._create_unverified_context()
  127. urllib.urlretrieve(url, filename, context=context)
  128. return filename
  129. except Exception:
  130. pass # fallback to no ssl context
  131. try:
  132. urllib.urlretrieve(url, filename)
  133. return filename
  134. except Exception, e:
  135. raise e
  136.  
  137. def download_bouquet(self, url):
  138. """Download panel bouquet file from url"""
  139. path = tempfile.gettempdir()
  140. filename = os.path.join(path, 'userbouquet.panel.tv')
  141. print("\n----Downloading providers bouquet file----")
  142. if DEBUG:
  143. print("bouqueturl = {}".format(url))
  144. try:
  145. urllib.urlretrieve(url, filename)
  146. except Exception, e:
  147. raise e
  148. return filename
  149.  
  150. def parse_panel_bouquet(self, panel_bouquet_file):
  151. """Check providers bouquet for custom service references
  152. """
  153. panel_bouquet = {}
  154.  
  155. if os.path.isfile(panel_bouquet_file):
  156. with open(panel_bouquet_file, "r") as f:
  157. for line in f:
  158. if '#SERVICE' in line:
  159. # get service ref values we need (dict value) and stream file (dict key)
  160. service = line.strip().split(':')
  161. if len(service) == 11:
  162. pos = service[10].rfind('/')
  163. if pos != -1 and (pos + 1 != len(service[10])):
  164. key = service[10][pos + 1:]
  165. value = ':'.join((service[3], service[4], service[5], service[6]))
  166. if value != '0:0:0:0':
  167. # only add to dict if a custom service id is present
  168. panel_bouquet[key] = value
  169. if not DEBUG:
  170. # remove panel bouquet file
  171. os.remove(panel_bouquet_file)
  172. return panel_bouquet
  173.  
  174. def parse_m3u(self, filename, all_iptv_stream_types, panel_bouquet, xcludesref,providername):
  175. """core parsing routine"""
  176. # Extract and generate the following items from the m3u
  177. # group-title
  178. # tvg-name
  179. # tvg-id
  180. # tvg-logo
  181. # stream-name
  182. # stream-url
  183.  
  184. print("\n----Parsing m3u file----")
  185. try:
  186. if not os.path.getsize(filename):
  187. raise Exception("M3U file is empty. Check username & password")
  188. except Exception, e:
  189. raise e
  190.  
  191. category_order = []
  192. category_options = {}
  193. dictchannels = OrderedDict()
  194. with open(filename, "r") as f:
  195. for line in f:
  196. if 'EXTM3U' in line: # First line we are not interested
  197. continue
  198. elif 'EXTINF:' in line: # Info line - work out group and output the line
  199. channeldict = {'tvg-id': '', 'tvg-name': '', 'tvg-logo': '', 'group-title': '', 'stream-name': '',
  200. 'stream-url': '',
  201. 'enabled': True,
  202. 'nameOverride': '',
  203. 'serviceRef': '',
  204. 'serviceRefOverride': False
  205. }
  206. channel = line.split('"')
  207. # strip unwanted info at start of line
  208. pos = channel[0].find(' ')
  209. channel[0] = channel[0][pos:]
  210.  
  211. # loop through params and build dict
  212. for i in xrange(0, len(channel) - 2, 2):
  213. channeldict[channel[i].lower().strip(' =')] = channel[i + 1].decode('utf-8')
  214.  
  215. # Get the stream name from end of line (after comma)
  216. stream_name_pos = line.rfind(',')
  217. if stream_name_pos != -1:
  218. channeldict['stream-name'] = line[stream_name_pos + 1:].strip().decode('utf-8')
  219.  
  220. # Set default name for any blank groups
  221. if channeldict['group-title'] == '':
  222. channeldict['group-title'] = u'None'
  223.  
  224. if not (channeldict['tvg-id'] or channeldict['group-title']):
  225. raise Exception("No extended playlist info found. Check m3u url should be 'type=m3u_plus'")
  226. elif 'http:' in line:
  227. channeldict['stream-url'] = line.strip()
  228.  
  229. self.set_streamtypes_vodcats(channeldict, all_iptv_stream_types)
  230.  
  231. if channeldict['group-title'] not in dictchannels:
  232. dictchannels[channeldict['group-title']] = [channeldict]
  233. else:
  234. dictchannels[channeldict['group-title']].append(channeldict)
  235.  
  236. category_order = dictchannels.keys()
  237.  
  238. # sort categories by custom order (if exists)
  239. sorted_categories, category_options = self.parse_map_bouquet_xml(dictchannels,providername)
  240. sorted_categories.extend(category_order)
  241. # remove duplicates, keep order
  242. category_order = OrderedDict((x, True) for x in sorted_categories).keys()
  243.  
  244. # Check for and parse override map
  245. self.parse_map_channels_xml(dictchannels, xcludesref,providername)
  246.  
  247. # Add Service references
  248. # VOD won't have epg so use same service id for all VOD
  249. vod_service_id = 65535
  250. serviceid_start = 34000
  251. category_offset = 150
  252. catstartnum = serviceid_start
  253.  
  254. for cat in category_order:
  255. num = catstartnum
  256. if cat in dictchannels:
  257. if not cat.startswith("VOD"):
  258. if cat in category_options:
  259. # check if we have cat idStart from override file
  260. if category_options[cat]["idStart"] > 0:
  261. num = category_options[cat]["idStart"]
  262. else:
  263. category_options[cat]["idStart"] = num
  264. else:
  265. category_options[cat] = {"idStart": num}
  266.  
  267. for x in dictchannels[cat]:
  268. cat_id = self.get_category_id(cat,providername)
  269. service_ref = "{:x}:{}:{}:0".format(num, cat_id[:4], cat_id[4:])
  270. if panel_bouquet:
  271. # check if we have the panels custom service ref
  272. pos = x['stream-url'].rfind('/')
  273. if pos != -1 and (pos + 1 != len(x['stream-url'])):
  274. m3u_stream_file = x['stream-url'][pos + 1:]
  275. if m3u_stream_file in panel_bouquet:
  276. # have a match use the panels custom service ref
  277. service_ref = panel_bouquet[m3u_stream_file]
  278. if not x['serviceRefOverride']:
  279. # if service ref is not overridden in xml update
  280. x['serviceRef'] = "{}:0:1:{}:0:0:0".format(x['stream-type'], service_ref)
  281. num += 1
  282. else:
  283. for x in dictchannels[cat]:
  284. x['serviceRef'] = "{}:0:1:{:x}:0:0:0:0:0:0".format(x['stream-type'], vod_service_id)
  285. while catstartnum < num:
  286. catstartnum += category_offset
  287.  
  288. # move all VOD categories to VOD placeholder position
  289. if ("VOD" in category_order):
  290. vodindex = category_order.index("VOD")
  291. vodcategories = list((cat for cat in category_order if cat.startswith('VOD -')))
  292. if len(vodcategories):
  293. # remove the multi vod categories from their current location
  294. category_order = [x for x in category_order if x not in vodcategories]
  295. # insert the multi vod categories at the placeholder pos
  296. category_order[vodindex:vodindex] = vodcategories
  297. category_order.remove("VOD")
  298.  
  299. # Have a look at what we have
  300. if DEBUG and TESTRUN:
  301. datafile = open(os.path.join(CFGPATH, 'channels.debug'), "w+")
  302. for cat in category_order:
  303. if cat in dictchannels:
  304. for line in dictchannels[cat]:
  305. linevals = ""
  306. for key, value in line.items():
  307. if type(value) is bool:
  308. linevals += str(value) + ":"
  309. else:
  310. linevals += (value).encode("utf-8") + ":"
  311. datafile.write("{}\n".format(linevals))
  312. datafile.close()
  313. print("Completed parsing data...")
  314.  
  315. if not DEBUG:
  316. # remove m3u file
  317. if os.path.isfile(filename):
  318. os.remove(filename)
  319.  
  320. return category_order, category_options, dictchannels
  321.  
  322. def set_streamtypes_vodcats(self, channeldict, all_iptv_stream_types):
  323. """Set the stream types and VOD categories
  324. """
  325. dato = channeldict['stream-url'].strip()
  326. dati = dato.split("/")
  327. last = len(dati) -1
  328. streamname = dati[last]
  329. if (channeldict['stream-url'].endswith('.ts') or channeldict['stream-url'].endswith('.m3u8') or streamname.find('.') == -1) \
  330. and not channeldict['group-title'].startswith('VOD'):
  331. channeldict['stream-type'] = '4097' if all_iptv_stream_types else '1'
  332. else:
  333. channeldict['group-title'] = u"VOD - {}".format(channeldict['group-title'])
  334. channeldict['stream-type'] = "4097"
  335.  
  336. def parse_map_bouquet_xml(self, dictchannels,providername):
  337. """Check for a mapping override file and parses it if found
  338. """
  339. category_order = []
  340. category_options = {}
  341. mapping_file = self.get_mapping_file(providername)
  342. if mapping_file:
  343. print("\n----Parsing custom bouquet order----")
  344.  
  345. with open(mapping_file, "r") as f:
  346. tree = ElementTree.parse(f)
  347. for node in tree.findall(".//category"):
  348. dictoption = {}
  349.  
  350. category = node.attrib.get('name')
  351. if not type(category) is unicode:
  352. category = category.decode("utf-8")
  353. cat_title_override = node.attrib.get('nameOverride', '')
  354. if not type(cat_title_override) is unicode:
  355. cat_title_override = cat_title_override.decode("utf-8")
  356. dictoption['nameOverride'] = cat_title_override
  357. dictoption['idStart'] = int(node.attrib.get('idStart', '0')) \
  358. if node.attrib.get('idStart', '0').isdigit() else 0
  359. if node.attrib.get('enabled') == 'false':
  360. dictoption["enabled"] = False
  361. # Remove category/bouquet
  362. if category != "VOD":
  363. if category in dictchannels:
  364. dictchannels.pop(category, None)
  365. else:
  366. keys_to_remove = []
  367. for k in dictchannels.iterkeys():
  368. if k.startswith("VOD"):
  369. keys_to_remove.append(k)
  370. if keys_to_remove:
  371. for k in keys_to_remove:
  372. dictchannels.pop(k, None)
  373. else:
  374. dictoption["enabled"] = True
  375. category_order.append(category)
  376.  
  377. category_options[category] = dictoption
  378.  
  379. print("custom bouquet order parsed...")
  380. return category_order, category_options
  381.  
  382. def parse_map_xmltvsources_xml(self,providername):
  383. """Check for a mapping override file and parses it if found
  384. """
  385. list_xmltv_sources = {}
  386. mapping_file = self.get_mapping_file(providername)
  387. if mapping_file:
  388. with open(mapping_file, "r") as f:
  389. tree = ElementTree.parse(f)
  390. for group in tree.findall('.//xmltvextrasources/group'):
  391. group_name = group.attrib.get('id')
  392. urllist = []
  393. for url in group:
  394. urllist.append(url.text)
  395. list_xmltv_sources[group_name] = urllist
  396. return list_xmltv_sources
  397.  
  398. def parse_map_channels_xml(self, dictchannels, xcludesref,providername):
  399. """Check for a mapping override file and applies it if found
  400. """
  401. mappingfile = self.get_mapping_file(providername)
  402. if mappingfile:
  403. print("\n----Parsing custom channel order, please be patient----")
  404.  
  405. with open(mappingfile, "r") as f:
  406. tree = ElementTree.parse(f)
  407. for cat in dictchannels:
  408. if not cat.startswith("VOD"):
  409. # We don't override any individual VOD streams
  410. print("sorting {}".format(cat.encode("utf-8")))
  411.  
  412. sortedchannels = []
  413. listchannels = []
  414. for x in dictchannels[cat]:
  415. listchannels.append(x['stream-name'])
  416. for node in tree.findall(u'.//channel[@category="{}"]'.format(cat)):
  417. sortedchannels.append(node.attrib.get('name'))
  418.  
  419. sortedchannels.extend(listchannels)
  420. # remove duplicates, keep order
  421. listchannels = OrderedDict((x, True) for x in sortedchannels).keys()
  422.  
  423. # sort the channels by new order
  424. channel_order_dict = {channel: index for index, channel in enumerate(listchannels)}
  425. dictchannels[cat].sort(key=lambda x: channel_order_dict[x['stream-name']])
  426.  
  427. for x in dictchannels[cat]:
  428. node = tree.find(u'.//channel[@name="{}"]'.format(x['stream-name']))
  429. if node is not None:
  430. if node.attrib.get('enabled') == 'false':
  431. x['enabled'] = False
  432. x['nameOverride'] = node.attrib.get('nameOverride', '')
  433. # default to current values if attribute doesn't exist
  434. x['tvg-id'] = node.attrib.get('tvg-id', x['tvg-id'])
  435. if node.attrib.get('serviceRef', None) and not xcludesref:
  436. x['serviceRef'] = node.attrib.get('serviceRef', x['serviceRef'])
  437. x['serviceRefOverride'] = True
  438. # streamUrl no longer output to xml file but we still check and process it
  439. x['stream-url'] = node.attrib.get('streamUrl', x['stream-url'])
  440. clear_stream_url = node.attrib.get('clearStreamUrl') == 'true'
  441. if clear_stream_url:
  442. x['stream-url'] = ''
  443.  
  444. print('custom channel order parsed...')
  445.  
  446. def save_map_xml(self, categoryorder, category_options, dictchannels, list_xmltv_sources,providername):
  447. """Create mapping file"""
  448. mappingfile = os.path.join(CFGPATH, self.get_safe_filename(providername) + '-sort-current.xml')
  449. indent = " "
  450. vod_category_output = False
  451.  
  452. if dictchannels:
  453. with open(mappingfile, "wb") as f:
  454. f.write('<!--\r\n')
  455. f.write('{} e2m3u2bouquet Custom mapping file\r\n'.format(indent))
  456. f.write('{} Rearrange bouquets or channels in the order you wish\r\n'.format(indent))
  457. f.write('{} Disable bouquets or channels by setting enabled to "false"\r\n'.format(indent))
  458. f.write('{} Map DVB EPG to IPTV by changing channel serviceRef attribute to match DVB service reference\r\n'.format(indent))
  459. f.write('{} Map XML EPG to different feed by changing channel tvg-id attribute\r\n'.format(indent))
  460. f.write('{} Rename this file as providername-sort-override.xml for changes to apply\r\n'.format(indent))
  461. f.write('-->\r\n')
  462.  
  463. f.write('<mapping>\r\n')
  464.  
  465. f.write('{}<xmltvextrasources>\r\n'.format(indent))
  466. if not list_xmltv_sources:
  467. # output example config
  468. f.write('{}<!-- Example Config\r\n'.format((2 * indent)))
  469. # uk
  470. f.write('{}<group id="{}">\r\n'.format(2 * indent, 'uk'))
  471. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://www.xmltvepg.nl/rytecxmltv-UK.gz'))
  472. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://rytecepg.ipservers.eu/epg_data/rytecxmltv-UK.gz'))
  473. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://rytecepg.wanwizard.eu/rytecxmltv-UK.gz'))
  474. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://91.121.106.172/~rytecepg/epg_data/rytecxmltv-UK.gz'))
  475. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://www.vuplus-community.net/rytec/rytecxmltv-UK.gz'))
  476. f.write('{}</group>\r\n'.format(2 * indent))
  477. # de
  478. f.write('{}<group id="{}">\r\n'.format(2 * indent, 'de'))
  479. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://www.xmltvepg.nl/rytecxmltvGermany.gz'))
  480. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://rytecepg.ipservers.eu/epg_data/rytecxmltvGermany.gz'))
  481. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://rytecepg.wanwizard.eu/rytecxmltvGermany.gz'))
  482. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://91.121.106.172/~rytecepg/epg_data/rytecxmltvGermany.gz'))
  483. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://www.vuplus-community.net/rytec/rytecxmltvGermany.gz'))
  484. f.write('{}</group>\r\n'.format(2 * indent))
  485. f.write('{}-->\r\n'.format(2 * indent))
  486. else:
  487. for group in list_xmltv_sources:
  488. f.write('{}<group id="{}">\r\n'.format(2 * indent, self.xml_escape(group)))
  489. for source in list_xmltv_sources[group]:
  490. f.write('{}<url>{}</url>\r\n'.format(3 * indent, self.xml_escape(source)))
  491. f.write('{}</group>\r\n'.format(2 * indent))
  492. f.write('{}</xmltvextrasources>\r\n'.format(indent))
  493.  
  494. f.write('{}<categories>\r\n'.format(indent))
  495. for cat in categoryorder:
  496. if cat in dictchannels:
  497. if not cat.startswith('VOD -'):
  498. cat_title_override = ''
  499. idStart = ''
  500. if cat in category_options:
  501. cat_title_override = category_options[cat].get('nameOverride', '')
  502. idStart = category_options[cat].get('idStart', '')
  503. f.write('{}<category name="{}" nameOverride="{}" idStart="{}" enabled="true" />\r\n'
  504. .format(2 * indent,
  505. self.xml_escape(cat).encode('utf-8'),
  506. self.xml_escape(cat_title_override).encode('utf-8'),
  507. idStart
  508. ))
  509. elif not vod_category_output:
  510. # Replace multivod categories with single VOD placeholder
  511. cat_title_override = ''
  512. if 'VOD' in category_options:
  513. cat_title_override = category_options['VOD'].get('nameOverride', '')
  514. f.write('{}<category name="{}" nameOverride="{}" enabled="true" />\r\n'
  515. .format(2 * indent,
  516. 'VOD',
  517. self.xml_escape(cat_title_override).encode('utf-8'),
  518. ))
  519. vod_category_output = True
  520. for cat in category_options:
  521. if 'enabled' in category_options[cat] and category_options[cat]['enabled'] is False:
  522. f.write('{}<category name="{}" nameOverride="{}" enabled="false" />\r\n'
  523. .format(2 * indent,
  524. self.xml_escape(cat).encode("utf-8"),
  525. self.xml_escape(cat_title_override).encode("utf-8")
  526. ))
  527.  
  528. f.write('{}</categories>\r\n'.format(indent))
  529.  
  530. f.write('{}<channels>\r\n'.format(indent))
  531. for cat in categoryorder:
  532. if cat in dictchannels:
  533. # Don't output any of the VOD channels
  534. if not cat.startswith('VOD'):
  535. f.write('{}<!-- {} -->\r\n'.format(2 * indent, self.xml_escape(cat.encode('utf-8'))))
  536. for x in dictchannels[cat]:
  537. f.write('{}<channel name="{}" nameOverride="{}" tvg-id="{}" enabled="{}" category="{}" serviceRef="{}" clearStreamUrl="{}" />\r\n'
  538. .format(2 * indent,
  539. self.xml_escape(x['stream-name'].encode('utf-8')),
  540. self.xml_escape(x.get('nameOverride', '').encode('utf-8')),
  541. self.xml_escape(x['tvg-id'].encode('utf-8')),
  542. str(x['enabled']).lower(),
  543. self.xml_escape(cat.encode('utf-8')),
  544. self.xml_escape(x['serviceRef']),
  545. 'false' if x['stream-url'] else 'true'
  546. ))
  547.  
  548. f.write('{}</channels>\r\n'.format(indent))
  549. f.write('</mapping>')
  550.  
  551. def download_picons(self, dictchannels, iconpath):
  552. print('\n----Downloading Picon files, please be patient----')
  553. print('If no Picons exist this will take a few minutes')
  554. if not os.path.isdir(iconpath):
  555. os.makedirs(iconpath)
  556.  
  557. for cat in dictchannels:
  558. if not cat.startswith('VOD'):
  559. # Download Picon if not VOD
  560. for x in dictchannels[cat]:
  561. self.download_picon_file(x['tvg-logo'], self.get_service_title(x), iconpath)
  562. print('\nPicons download completed...')
  563. print('Box will need restarted for Picons to show...')
  564.  
  565. def download_picon_file(self, logourl, title, iconpath):
  566. if logourl:
  567. if not logourl.startswith('http'):
  568. logourl = 'http://{}'.format(logourl)
  569. piconname = self.get_picon_name(title)
  570. piconfilepath = os.path.join(iconpath, piconname)
  571. existingpicon = filter(os.path.isfile, glob.glob(piconfilepath + '*'))
  572.  
  573. if not existingpicon:
  574. if DEBUG:
  575. print("Picon file doesn't exist downloading")
  576. print('PiconURL: {}'.format(logourl))
  577. else:
  578. # Output some kind of progress indicator
  579. sys.stdout.write('.')
  580. sys.stdout.flush()
  581. try:
  582. urllib.urlretrieve(logourl, piconfilepath)
  583. except Exception, e:
  584. if DEBUG:
  585. print(e)
  586. return
  587. self.picon_post_processing(piconfilepath)
  588.  
  589. def picon_post_processing(self, piconfilepath):
  590. """Check type of image received and convert to png
  591. if necessary
  592. """
  593. ext = ""
  594. # get image type
  595. try:
  596. ext = imghdr.what(piconfilepath)
  597. except Exception, e:
  598. if DEBUG:
  599. print(e)
  600. return
  601. # if image but not png convert to png
  602. if (ext is not None) and (ext is not 'png'):
  603. if DEBUG:
  604. print('Converting Picon to png')
  605. try:
  606. Image.open(piconfilepath).save("{}.{}".format(piconfilepath, 'png'))
  607. except Exception, e:
  608. if DEBUG:
  609. print(e)
  610. return
  611. try:
  612. # remove non png file
  613. os.remove(piconfilepath)
  614. except Exception, e:
  615. if DEBUG:
  616. print(e)
  617. return
  618. else:
  619. # rename to correct extension
  620. try:
  621. os.rename(piconfilepath, "{}.{}".format(piconfilepath, ext))
  622. except Exception, e:
  623. if DEBUG:
  624. print(e)
  625. pass
  626.  
  627. def get_picon_name(self, serviceName):
  628. """Convert the service name to a Picon Service Name
  629. """
  630. name = serviceName
  631. if type(name) is unicode:
  632. name = name.encode('utf-8')
  633. name = unicodedata.normalize('NFKD', unicode(name, 'utf_8')).encode('ASCII', 'ignore')
  634. exclude_chars = ['/', '\\', '\'', '"', '`', '?', ' ', '(', ')', ':', '<', '>', '|', '.', '\n', '!']
  635. name = re.sub('[%s]' % ''.join(exclude_chars), '', name)
  636. name = name.replace('&', 'and')
  637. name = name.replace('+', 'plus')
  638. name = name.replace('*', 'star')
  639. name = name.lower()
  640. return name
  641.  
  642. def get_safe_filename(self, filename):
  643. """Convert filename to safe filename
  644. """
  645. name = filename.replace(" ", "_").replace("/", "_")
  646. if type(name) is unicode:
  647. name = name.encode('utf-8')
  648. name = unicodedata.normalize('NFKD', unicode(name, 'utf_8')).encode('ASCII', 'ignore')
  649. exclude_chars = ['/', '\\', '\'', '"', '`',
  650. '?', ' ', '(', ')', ':', '<', '>',
  651. '|', '.', '\n', '!', '&', '+', '*']
  652. name = re.sub('[%s]' % ''.join(exclude_chars), '', name)
  653. name = name.lower()
  654. return name
  655.  
  656. def get_current_bouquet_indexes(self,providername):
  657. """Get the bouquet indexes (ex iptv)
  658. """
  659. current_bouquets_indexes =[]
  660.  
  661. with open(os.path.join(ENIGMAPATH, 'bouquets.tv'), 'r') as f:
  662. for line in f:
  663. if line.startswith('#NAME'):
  664. continue
  665. else:
  666. if not '.suls_iptv_'+self.get_safe_filename(providername) in line:
  667. current_bouquets_indexes.append(line)
  668. return current_bouquets_indexes
  669.  
  670. def create_bouquets(self, category_order, category_options, dictchannels, multivod, allbouquet, bouquettop,providername):
  671. """Create the Enigma2 bouquets
  672. """
  673. print("\n----Creating bouquets----")
  674. # clean old bouquets before writing new
  675. if dictchannels:
  676. for fname in os.listdir(ENIGMAPATH):
  677. if 'userbouquet.suls_iptv_'+self.get_safe_filename(providername) in fname:
  678. os.remove(os.path.join(ENIGMAPATH, fname))
  679. iptv_bouquet_list = []
  680.  
  681. if allbouquet:
  682. iptv_bouquet_list = self.create_all_channels_bouquet(category_order, category_options, dictchannels,providername)
  683.  
  684. vod_categories = list(cat for cat in category_order if cat.startswith('VOD -'))
  685. vod_category_output = False
  686. vod_bouquet_entry_output = False
  687. channel_number_start_offset_output = False
  688.  
  689. for cat in category_order:
  690. if cat in dictchannels:
  691. cat_title = self.get_category_title(cat, category_options)
  692. # create file
  693. cat_filename = self.get_safe_filename(cat_title)
  694.  
  695. if cat in vod_categories and not multivod:
  696. cat_filename = "VOD"
  697.  
  698. bouquet_filepath = os.path.join(ENIGMAPATH, 'userbouquet.suls_iptv_' + self.get_safe_filename(providername) +'_{}.tv'
  699. .format(cat_filename))
  700. if DEBUG:
  701. print("Creating: {}".format(bouquet_filepath))
  702.  
  703. if cat not in vod_categories or multivod:
  704. with open(bouquet_filepath, "w+") as f:
  705. bouquet_name = self.get_safe_filename(providername)+ ' - {}'.format(cat_title).decode("utf-8")
  706. if not cat.startswith('VOD -'):
  707. if cat in category_options and category_options[cat].get('nameOverride', False):
  708. bouquet_name = category_options[cat]['nameOverride'].decode('utf-8')
  709. else:
  710. if 'VOD' in category_options and category_options['VOD'].get('nameOverride', False):
  711. bouquet_name = '{} - {}'\
  712. .format(category_options['VOD']['nameOverride'].decode('utf-8'),
  713. cat_title.replace('VOD - ', '').decode("utf-8"))
  714. channel_num = 0
  715. f.write("#NAME {}\n".format(bouquet_name.encode("utf-8")))
  716. if not channel_number_start_offset_output and not allbouquet:
  717. # write place holder services (for channel numbering)
  718. for i in xrange(100):
  719. f.write('#SERVICE 1:832:d:0:0:0:0:0:0:0:\n')
  720. channel_number_start_offset_output = True
  721. channel_num += 1
  722.  
  723. for x in dictchannels[cat]:
  724. if x['enabled']:
  725. self.save_bouquet_entry(f, x)
  726. channel_num += 1
  727.  
  728. while (channel_num % 100) is not 0:
  729. f.write('#SERVICE 1:832:d:0:0:0:0:0:0:0:\n')
  730. channel_num += 1
  731. elif not vod_category_output and not multivod:
  732. # not multivod - output all the vod services in one file
  733. with open(bouquet_filepath, "w+") as f:
  734. bouquet_name = self.get_safe_filename(providername) + ' - VOD'.decode("utf-8")
  735. if 'VOD' in category_options and category_options['VOD'].get('nameOverride', False):
  736. bouquet_name = category_options['VOD']['nameOverride'].decode('utf-8')
  737.  
  738. channel_num = 0
  739. f.write("#NAME {}\n".format(bouquet_name.encode("utf-8")))
  740. if not channel_number_start_offset_output and not allbouquet:
  741. # write place holder services (for channel numbering)
  742. for i in xrange(100):
  743. f.write('#SERVICE 1:832:d:0:0:0:0:0:0:0:\n')
  744. channel_number_start_offset_output = True
  745. channel_num += 1
  746.  
  747. for vodcat in vod_categories:
  748. if vodcat in dictchannels:
  749. # Insert group description placeholder in bouquet
  750. f.write("#SERVICE 1:64:0:0:0:0:0:0:0:0:\n")
  751. f.write("#DESCRIPTION {}\n". format(vodcat))
  752. for x in dictchannels[vodcat]:
  753. self.save_bouquet_entry(f, x)
  754. channel_num += 1
  755.  
  756. while (channel_num % 100) is not 0:
  757. f.write('#SERVICE 1:832:d:0:0:0:0:0:0:0:\n')
  758. channel_num += 1
  759. vod_category_output = True
  760.  
  761. # Add to bouquet index list
  762. if cat not in vod_categories or (cat in vod_categories and not vod_bouquet_entry_output):
  763. iptv_bouquet_list.append(self.get_bouquet_index_name(cat_filename,self.get_safe_filename(providername)))
  764. if cat in vod_categories and not multivod:
  765. vod_bouquet_entry_output = True
  766.  
  767. # write the bouquets.tv indexes
  768. self.save_bouquet_index_entries(iptv_bouquet_list, bouquettop,providername)
  769.  
  770. print("bouquets created ...")
  771.  
  772. def create_all_channels_bouquet(self, category_order, category_options, dictchannels,providername):
  773. """Create the Enigma2 all channels bouquet
  774. """
  775. print("\n----Creating all channels bouquet----")
  776.  
  777. bouquet_indexes = []
  778.  
  779. vod_categories = list(cat for cat in category_order if cat.startswith('VOD -'))
  780. bouquet_name = self.get_safe_filename(providername) + "All Channels"
  781. cat_filename = self.get_safe_filename(bouquet_name)
  782.  
  783. # create file
  784. bouquet_filepath = os.path.join(ENIGMAPATH, 'userbouquet.suls_iptv_' + self.get_safe_filename(providername) + '_{}.tv'
  785. .format(cat_filename))
  786. if DEBUG:
  787. print("Creating: {}".format(bouquet_filepath))
  788.  
  789. with open(bouquet_filepath, "w+") as f:
  790. f.write("#NAME " + providername + " - {}\n".format(bouquet_name.encode("utf-8")))
  791.  
  792. # write place holder channels (for channel numbering)
  793. for i in xrange(100):
  794. f.write('#SERVICE 1:832:d:0:0:0:0:0:0:0:\n')
  795. channel_num = 1
  796.  
  797. for cat in category_order:
  798. if cat in dictchannels:
  799. if cat not in vod_categories:
  800. cat_title = self.get_category_title(cat, category_options)
  801. # Insert group description placeholder in bouquet
  802. f.write("#SERVICE 1:64:0:0:0:0:0:0:0:0:\n")
  803. f.write("#DESCRIPTION {}\n".format(cat_title))
  804. for x in dictchannels[cat]:
  805. if x['enabled']:
  806. self.save_bouquet_entry(f, x)
  807. channel_num += 1
  808.  
  809. while (channel_num % 100) is not 0:
  810. f.write('#SERVICE 1:832:d:0:0:0:0:0:0:0:\n')
  811. channel_num += 1
  812.  
  813. # Add to bouquet index list
  814. bouquet_indexes.append(self.get_bouquet_index_name(cat_filename,self.get_safe_filename(providername)))
  815. print("all channels bouquet created ...")
  816. return bouquet_indexes
  817.  
  818. def save_bouquet_entry(self, f, channel):
  819. """Add service to bouquet file
  820. """
  821. f.write("#SERVICE {}:{}:{}\n"
  822. .format(channel['serviceRef'], channel['stream-url']
  823. .replace(":", "%3a"), self.get_service_title(channel).encode("utf-8")))
  824. f.write("#DESCRIPTION {}\n".format(self.get_service_title(channel).encode("utf-8")))
  825.  
  826. def get_bouquet_index_name(self, filename,providername):
  827. return ('#SERVICE 1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.suls_iptv_'+self.get_safe_filename(providername) +'_{}.tv" ORDER BY bouquet\n'
  828. .format(filename))
  829.  
  830. def save_bouquet_index_entries(self, iptv_bouquets, bouquettop,providername):
  831. """Add to the main bouquets.tv file
  832. """
  833. # get current bouquets indexes
  834. current_bouquet_indexes = self.get_current_bouquet_indexes(providername)
  835.  
  836. if iptv_bouquets:
  837. with open(os.path.join(ENIGMAPATH, 'bouquets.tv'), 'w') as f:
  838. f.write('#NAME Bouquets (TV)\n')
  839. if bouquettop:
  840. for bouquet in iptv_bouquets:
  841. f.write(bouquet)
  842. for bouquet in current_bouquet_indexes:
  843. f.write(bouquet)
  844. else:
  845. for bouquet in current_bouquet_indexes:
  846. f.write(bouquet)
  847. for bouquet in iptv_bouquets:
  848. f.write(bouquet)
  849.  
  850. def reload_bouquets(self):
  851. if not TESTRUN:
  852. print("\n----Reloading bouquets----")
  853. os.system("wget -qO - http://127.0.0.1/web/servicelistreload?mode=2 > /dev/null 2>&1 &")
  854. print("bouquets reloaded...")
  855.  
  856. def create_epgimporter_config(self, categoryorder, category_options, dictchannels, list_xmltv_sources, epgurl, provider):
  857. indent = " "
  858. if DEBUG:
  859. print("creating EPGImporter config")
  860. # create channels file
  861. if not os.path.isdir(EPGIMPORTPATH):
  862. os.makedirs(EPGIMPORTPATH)
  863. channels_filename = os.path.join(EPGIMPORTPATH, 'suls_iptv_' + self.get_safe_filename(provider) + '_channels.xml')
  864.  
  865. if dictchannels:
  866. with open(channels_filename, "w+") as f:
  867. f.write('<channels>\n')
  868. for cat in categoryorder:
  869. if cat in dictchannels:
  870. if not cat.startswith('VOD'):
  871. cat_title = self.get_category_title(cat, category_options)
  872.  
  873. f.write('{}<!-- {} -->\n'.format(indent, self.xml_escape(cat_title.encode('utf-8'))))
  874. for x in dictchannels[cat]:
  875. tvg_id = x['tvg-id'] if x['tvg-id'] else self.get_service_title(x)
  876. if x['enabled']:
  877. f.write('{}<channel id="{}">{}:http%3a//example.m3u8</channel> <!-- {} -->\n'
  878. .format(indent, self.xml_escape(tvg_id.encode('utf-8')), x['serviceRef'],
  879. self.xml_escape(self.get_service_title(x).encode('utf-8'))))
  880. f.write("</channels>\n")
  881.  
  882. # create epg-importer sources file for providers feed
  883. self.create_epgimport_source([epgurl], provider)
  884.  
  885. # create epg-importer sources file for additional feeds
  886. for group in list_xmltv_sources:
  887. self.create_epgimport_source(list_xmltv_sources[group], '{} - {}'.format(provider, group),provider)
  888.  
  889. def create_epgimport_source(self, sources, source_name):
  890. """Create epg-importer source file
  891. """
  892. indent = " "
  893. channels_filename = os.path.join(EPGIMPORTPATH, 'suls_iptv_' + self.get_safe_filename(source_name) + '_channels.xml')
  894.  
  895. # write providers epg feed
  896. source_filename = os.path.join(EPGIMPORTPATH, 'suls_iptv_{}.sources.xml'
  897. .format(self.get_safe_filename(source_name)))
  898.  
  899. with open(os.path.join(EPGIMPORTPATH, source_filename), "w+") as f:
  900. f.write('<sources>\n')
  901. f.write('{}<sourcecat sourcecatname="IPTV Bouquet Maker - E2m3u2bouquet">\n'.format(indent))
  902. f.write('{}<source type="gen_xmltv" channels="{}">\n'
  903. .format(2 * indent, channels_filename))
  904. f.write('{}<description>{}</description>\n'.format(3 * indent, self.xml_escape(source_name)))
  905. for source in sources:
  906. f.write('{}<url>{}</url>\n'.format(3 * indent, self.xml_escape(source)))
  907. f.write('{}</source>\n'.format(2 * indent))
  908. f.write('{}</sourcecat>\n'.format(indent))
  909. f.write('</sources>\n')
  910.  
  911. def read_providers(self,providerfile):
  912. # Check we have data
  913. try:
  914. if not os.path.getsize(providerfile):
  915. raise Exception('Providers file is empty')
  916. except Exception, e:
  917. raise e
  918. with open(providerfile, "r") as f:
  919. for line in f:
  920. if line == "400: Invalid request\n":
  921. print("Providers download is invalid please resolve or use URL based setup")
  922. sys(exit(1))
  923. line = base64.b64decode(line)
  924. if line:
  925. provider = {
  926. 'name': line.split(',')[0],
  927. 'm3u': line.split(',')[1],
  928. 'epg': line.split(',')[2]
  929. }
  930. PROVIDERS[provider['name']] = provider
  931.  
  932. if not DEBUG:
  933. # remove providers file
  934. os.remove(providerfile)
  935. return PROVIDERS
  936.  
  937. def process_provider(self, provider, username, password):
  938. supported_providers = ""
  939. for line in PROVIDERS:
  940. supported_providers += " " + PROVIDERS[line]['name']
  941. if PROVIDERS[line]['name'].upper() == provider.upper():
  942. if DEBUG:
  943. print("----Provider setup details----")
  944. print("m3u = " + PROVIDERS[line]['m3u'].replace("USERNAME", username).replace("PASSWORD", password))
  945. print("epg = " + PROVIDERS[line]['epg'].replace("USERNAME", username).replace("PASSWORD", password) + "\n")
  946. return PROVIDERS[line]['m3u'].replace("USERNAME", username).replace("PASSWORD", password), \
  947. PROVIDERS[line]['epg'].replace("USERNAME", username).replace("PASSWORD", password), \
  948. supported_providers
  949. # If we get here the supplied provider is invalid
  950. return "NOTFOUND", "", 0, 0, 0, 0, supported_providers
  951.  
  952. def get_mapping_file(self,providername):
  953. mapping_file = None
  954. search_path = [os.path.join(CFGPATH, self.get_safe_filename(providername) + '-sort-override.xml'),
  955. os.path.join(os.getcwd(), self.get_safe_filename(providername) + '-sort-override.xml')]
  956. for path in search_path:
  957. if os.path.isfile(path):
  958. mapping_file = path
  959. break;
  960. return mapping_file
  961.  
  962. def xml_escape(self, string):
  963. return string.replace("&", "&amp;") \
  964. .replace("\"", "&quot;") \
  965. .replace("'", "&apos;") \
  966. .replace("<", "&lt;") \
  967. .replace(">", "&gt;")
  968.  
  969. def xml_unescape(self, string):
  970. return string.replace('&quot;', '"') \
  971. .replace() \
  972. .replace("&apos;", "'") \
  973. .replace("&lt;", "<") \
  974. .replace("&gt;", ">") \
  975. .replace("&amp;", "&")
  976.  
  977. def get_service_title(self, channel):
  978. """Return the title override if set else the title
  979. """
  980. return channel['nameOverride'] if channel.get('nameOverride', False) else channel['stream-name']
  981.  
  982. def get_category_title(self, cat, category_options):
  983. """Return the title override if set else the title
  984. """
  985. if cat in category_options:
  986. return category_options[cat]['nameOverride'] if category_options[cat].get('nameOverride', False) else cat
  987. return cat
  988.  
  989. def get_category_id(self, cat,providername):
  990. """Generate 32 bit category id to help make service refs unique"""
  991. return hashlib.md5(providername+ cat).hexdigest()[:8]
  992.  
  993. class config:
  994.  
  995. def makeconfig(self,configfile):
  996. print('Default configuration file created in '+ CFGPATH + 'config.xml\n')
  997.  
  998. f= open(configfile, "w+")
  999. f.write("""<!-- E2m3u2bouquet supplier config file -->
  1000. <!-- Add as many suppliers as required and run the script with no parameters -->
  1001. <!-- this config file will be used and the relevant bouquets set up for all suppliers entered -->
  1002. <!-- 0 = No/false -->
  1003. <!-- 1 = Yes/true -->
  1004. <config>
  1005. <supplier>
  1006. <name>Supplier Name</name> <!-- Supplier Name -->
  1007. <enabled>1</enabled> <!-- Enable or disable the supplier (0 or 1) -->
  1008. <m3uurl><![CDATA[http://address.yourprovider.com:80/get.php?username=YOURUSERNAME&password=YOURPASSWORD&type=m3u_plus&output=ts]]></m3uurl> <!-- Extended M3U URL including your username and password -->
  1009. <epgurl><![CDATA[http://address.yourprovider.com:80/xmltv.php?username=YOURUSERNAME&password=YOURPASSWORD]]></epgurl> <!-- XML EPG URL including your username and password -->
  1010. <iptvtypes>0</iptvtypes> <!-- Change all streams to IPTV type (0 or 1) -->
  1011. <multivod>0</multivod> <!-- Split VOD into seperate categories (0 or 1) -->
  1012. <allbouquet>1</allbouquet> <!-- Create all channels bouquet -->
  1013. <picons>0</picons> <!-- Automatically download Picons (0 or 1) -->
  1014. <iconpath>/usr/share/enigma2/picon/</iconpath> <!-- Location to store picons -->
  1015. <xcludesref>0</xcludesref> <!-- Disable service ref overriding from override.xml file (0 or 1) -->
  1016. <bouqueturl></bouqueturl> <!-- URL to download providers bouquet - to map custom service references -->
  1017. <bouquetdownload>0</bouquetdownload> <!-- Download providers bouquet (use default url) - to map custom service references -->
  1018. <bouquettop>0</bouquettop> <!-- Place IPTV bouquets at top (0 or 1)-->
  1019. </supplier>
  1020. <supplier>
  1021. <name>Supplier Name</name> <!-- Supplier Name -->
  1022. <enabled>0</enabled> <!-- Enable or disable the supplier (0 or 1) -->
  1023. <m3uurl><![CDATA[http://address.yourprovider.com:80/get.php?username=YOURUSERNAME&password=YOURPASSWORD&type=m3u_plus&output=ts]]></m3uurl> <!-- Extended M3U URL including your username and password -->
  1024. <epgurl><![CDATA[http://address.yourprovider.com:80/xmltv.php?username=YOURUSERNAME&password=YOURPASSWORD]]></epgurl> <!-- XML EPG URL including your username and password -->
  1025. <iptvtypes>0</iptvtypes> <!-- Change all streams to IPTV type (0 or 1) -->
  1026. <multivod>0</multivod> <!-- Split VOD into seperate categories (0 or 1) -->
  1027. <allbouquet>1</allbouquet> <!-- Create all channels bouquet -->
  1028. <picons>0</picons> <!-- Automatically download Picons (0 or 1) -->
  1029. <iconpath>/usr/share/enigma2/picon/</iconpath> <!-- Location to store picons -->
  1030. <xcludesref>0</xcludesref> <!-- Disable service ref overriding from override.xml file (0 or 1) -->
  1031. <bouqueturl></bouqueturl> <!-- URL to download providers bouquet - to map custom service references -->
  1032. <bouquetdownload>0</bouquetdownload> <!-- Download providers bouquet (use default url) - to map custom service references -->
  1033. <bouquettop>0</bouquettop> <!-- Place IPTV bouquets at top (0 or 1)-->
  1034. </supplier>
  1035. </config>""")
  1036.  
  1037. def readconfig(self,configfile):
  1038. suppliers = []
  1039. with open(configfile, "r") as f:
  1040. tree = ElementTree.parse(f)
  1041. for node in tree.findall(".//supplier"):
  1042. supplier = {}
  1043. for child in node:
  1044. if (DEBUG == 1) or (TESTRUN == 1):
  1045. print(child.tag + " = " + str("" if child.text is None else child.text))
  1046. supplier[child.tag] = str("" if child.text is None else child.text)
  1047. suppliers.append(supplier)
  1048. return suppliers
  1049.  
  1050. def run_e2m3u2bouquet(self,provider):
  1051. # Build up our args
  1052. newargs = []
  1053. newargs.append('-n={}'.format(provider["name"]))
  1054. newargs.append('-m={}'.format(provider["m3uurl"]))
  1055. newargs.append('-e={}'.format(provider["epgurl"]))
  1056. if provider["iptvtypes"]=="1":
  1057. newargs.append('-i')
  1058. if provider["multivod"]=="1":
  1059. newargs.append('-M')
  1060. if provider["allbouquet"]=="1":
  1061. newargs.append('-a')
  1062. if provider["picons"]=="1":
  1063. newargs.append('-P')
  1064. newargs.append('-q={}'.format(provider["iconpath"]))
  1065. if provider["xcludesref"]=="1":
  1066. newargs.append('-xs')
  1067. if provider["bouquettop"]=="1":
  1068. newargs.append('-bt')
  1069. if provider["bouquetdownload"]=="1":
  1070. newargs.append('-bd')
  1071. newargs.append('-b={}'.format(provider["bouqueturl"]))
  1072. # Recall ourselves
  1073. main(newargs)
  1074.  
  1075. def main(argv=None): # IGNORE:C0111
  1076. # Command line options.
  1077. if argv is None:
  1078. argv = sys.argv
  1079. else:
  1080. sys.argv.extend(argv)
  1081. program_name = os.path.basename(sys.argv[0])
  1082. program_version = "v%s" % __version__
  1083. program_build_date = str(__updated__)
  1084. program_version_message = '%(prog)s {} ({})'.format(program_version, program_build_date)
  1085. program_shortdesc = __doc__.split("\n")[1]
  1086. program_license = """{}
  1087.  
  1088. Copyright 2017. All rights reserved.
  1089. Created on {}.
  1090. Licensed under GNU GENERAL PUBLIC LICENSE version 3
  1091. Distributed on an "AS IS" basis without warranties
  1092. or conditions of any kind, either express or implied.
  1093.  
  1094. USAGE
  1095. """.format(program_shortdesc, str(__date__))
  1096.  
  1097. try:
  1098. # Setup argument parser
  1099. parser = ArgumentParser(description=program_license, formatter_class=RawDescriptionHelpFormatter)
  1100. # URL Based Setup
  1101. urlgroup = parser.add_argument_group('URL Based Setup')
  1102. urlgroup.add_argument('-m', '--m3uurl', dest='m3uurl', action='store',
  1103. help='URL to download m3u data from (required)')
  1104. urlgroup.add_argument('-e', '--epgurl', dest='epgurl', action='store',
  1105. help='URL source for XML TV epg data sources')
  1106. # Provider based setup
  1107. providergroup = parser.add_argument_group('Provider Based Setup')
  1108. providergroup.add_argument('-n', '--providername', dest='providername', action='store',
  1109. help='Host IPTV provider name (e.g. FAB/EPIC) (required)')
  1110. providergroup.add_argument('-u', '--username', dest='username', action='store',
  1111. help='Your IPTV username (required)')
  1112. providergroup.add_argument('-p', '--password', dest='password', action='store',
  1113. help='Your IPTV password (required)')
  1114. # Options
  1115. parser.add_argument('-i', '--iptvtypes', dest='iptvtypes', action='store_true',
  1116. help='Treat all stream references as IPTV stream type. (required for some enigma boxes)')
  1117. parser.add_argument('-M', '--multivod', dest='multivod', action='store_true',
  1118. help='Create multiple VOD bouquets rather single VOD bouquet')
  1119. parser.add_argument('-a', '--allbouquet', dest='allbouquet', action='store_true',
  1120. help='Create all channels bouquet')
  1121. parser.add_argument('-P', '--picons', dest='picons', action='store_true',
  1122. help='Automatically download of Picons, this option will slow the execution')
  1123. parser.add_argument('-q', '--iconpath', dest='iconpath', action='store',
  1124. help='Option path to store picons, if not supplied defaults to /usr/share/enigma2/picon/')
  1125. parser.add_argument('-xs', '--xcludesref', dest='xcludesref', action='store_true',
  1126. help='Disable service ref overriding from override.xml file')
  1127. parser.add_argument('-b', '--bouqueturl', dest='bouqueturl', action='store',
  1128. help='URL to download providers bouquet - to map custom service references')
  1129. parser.add_argument('-bd', '--bouquetdownload', dest='bouquetdownload', action='store_true',
  1130. help='Download providers bouquet (use default url) - to map custom service references')
  1131. parser.add_argument('-bt', '--bouquettop', dest='bouquettop', action='store_true',
  1132. help='Place IPTV bouquets at top')
  1133. parser.add_argument('-U', '--uninstall', dest='uninstall', action='store_true',
  1134. help='Uninstall all changes made by this script')
  1135. parser.add_argument('-V', '--version', action='version', version=program_version_message)
  1136.  
  1137. # Process arguments
  1138. args = parser.parse_args()
  1139. m3uurl = args.m3uurl
  1140. epgurl = args.epgurl
  1141. iptvtypes = args.iptvtypes
  1142. uninstall = args.uninstall
  1143. multivod = args.multivod
  1144. allbouquet = args.allbouquet
  1145. bouquet_url = args.bouqueturl
  1146. bouquet_download = args.bouquetdownload
  1147. picons = args.picons
  1148. iconpath = args.iconpath
  1149. xcludesref = args.xcludesref
  1150. bouquettop = args.bouquettop
  1151. provider = args.providername
  1152. username = args.username
  1153. password = args.password
  1154. # Set epg to rytec if nothing else provided
  1155. if epgurl is None:
  1156. epgurl = "http://www.vuplus-community.net/rytec/rytecxmltv-UK.gz"
  1157. # Set piconpath
  1158. if iconpath is None:
  1159. iconpath = PICONSPATH
  1160. if provider is None:
  1161. provider = "E2m3u2Bouquet"
  1162. # Check we have enough to proceed
  1163. if (m3uurl is None) and ((provider is None) or (username is None) or (password is None)) and uninstall is False:
  1164. print('\n********************************')
  1165. print('E2m3u2bouquet - Config based setup')
  1166. print("********************************\n")
  1167. configs = config()
  1168. if os.path.isfile(CFGPATH+"config.xml"):
  1169. supplierslist = configs.readconfig(CFGPATH+"config.xml")
  1170. for supplier in supplierslist:
  1171. if supplier["enabled"]=="1":
  1172. if supplier["name"]=="Supplier Name":
  1173. print("Please enter your details in the supplier config file in - " + CFGPATH+"config.xml")
  1174. sys.exit(2)
  1175. else:
  1176. print('\n********************************')
  1177. print('Config based setup - '+ supplier["name"])
  1178. print("********************************\n")
  1179. configs.run_e2m3u2bouquet(supplier)
  1180. else:
  1181. print("\nSupplier: "+supplier["name"] + " is disabled - skipping.........\n")
  1182. sys.exit(0)
  1183. else:
  1184. configs.makeconfig(CFGPATH+"config.xml")
  1185. print('Please ensure correct command line options are passed to the program \n'
  1186. 'or populate the config file in ' + CFGPATH + "config.xml \n" +
  1187. 'for help use --help\n')
  1188. parser.print_usage()
  1189. sys.exit(1)
  1190.  
  1191. except KeyboardInterrupt:
  1192. ### handle keyboard interrupt ###
  1193. return 0
  1194.  
  1195. except Exception, e:
  1196. if DEBUG or TESTRUN:
  1197. raise e
  1198. indent = len(program_name) * " "
  1199. sys.stderr.write(program_name + ": " + repr(e) + "\n")
  1200. sys.stderr.write(indent + " for help use --help")
  1201. return 2
  1202.  
  1203. # # Core program logic starts here
  1204. urllib._urlopener = AppUrlOpener()
  1205. e2m3uSetup = IPTVSetup()
  1206. if uninstall:
  1207. # Clean up any existing files
  1208. e2m3uSetup.uninstaller()
  1209. # reload bouquets
  1210. e2m3uSetup.reload_bouquets()
  1211. print("Uninstall only, program exiting ...")
  1212. sys.exit(1) # Quit here if we just want to uninstall
  1213. else:
  1214. # create config folder if it doesn't exist
  1215. if not os.path.isdir(CFGPATH):
  1216. os.makedirs(CFGPATH)
  1217.  
  1218. # Work out provider based setup if that's what we have
  1219. if (provider is not None) and (username is not None) or (password is not None):
  1220. providersfile = e2m3uSetup.download_providers(PROVIDERSURL)
  1221. e2m3uSetup.read_providers(providersfile)
  1222. m3uurl, epgurl, supported_providers = e2m3uSetup.process_provider(
  1223. provider, username, password)
  1224. if m3uurl == "NOTFOUND":
  1225. print("----ERROR----")
  1226. print("Provider not found, supported providers = " + supported_providers)
  1227. sys(exit(1))
  1228.  
  1229. # get default provider bouquet download url if bouquet download set and no bouquet url given
  1230. if bouquet_download and not bouquet_url:
  1231. # set bouquet_url to default url
  1232. pos = m3uurl.find('get.php')
  1233. if pos != -1:
  1234. bouquet_url = m3uurl[0:pos + 7] + '?username={}&password={}&type=dreambox&output=ts'.format(
  1235. username, password)
  1236. # Download panel bouquet
  1237. panel_bouquet = None
  1238. if bouquet_url:
  1239. panel_bouquet_file = e2m3uSetup.download_bouquet(bouquet_url)
  1240. panel_bouquet = e2m3uSetup.parse_panel_bouquet(panel_bouquet_file)
  1241. # Download m3u
  1242. m3ufile = e2m3uSetup.download_m3u(m3uurl)
  1243. # parse m3u file
  1244. categoryorder, category_options, dictchannels = e2m3uSetup.parse_m3u(m3ufile, iptvtypes, panel_bouquet,
  1245. xcludesref,provider)
  1246. list_xmltv_sources = e2m3uSetup.parse_map_xmltvsources_xml(provider)
  1247. # save xml mapping - should be after m3u parsing
  1248. e2m3uSetup.save_map_xml(categoryorder, category_options, dictchannels, list_xmltv_sources,provider)
  1249.  
  1250. #download picons
  1251. if picons:
  1252. e2m3uSetup.download_picons(dictchannels, iconpath)
  1253. # Create bouquet files
  1254. e2m3uSetup.create_bouquets(categoryorder, category_options, dictchannels, multivod, allbouquet, bouquettop,provider)
  1255. # Now create custom channels for each bouquet
  1256. print("\n----Creating EPG-Importer config ----")
  1257. e2m3uSetup.create_epgimporter_config(categoryorder, category_options, dictchannels, list_xmltv_sources, epgurl, provider)
  1258. print("EPG-Importer config created...")
  1259. # reload bouquets
  1260. e2m3uSetup.reload_bouquets()
  1261. print("\n********************************")
  1262. print("Engima2 IPTV bouquets created ! ")
  1263. print("********************************")
  1264. print("\nTo enable EPG data")
  1265. print("Please open EPG-Importer plugin.. ")
  1266. print("Select sources and enable the new IPTV source")
  1267. print("(will be listed as {} under 'IPTV Bouquet Maker - E2m3u2bouquet')".format(provider))
  1268. print("Save the selected sources, press yellow button to start manual import")
  1269. print("You can then set EPG-Importer to automatically import the EPG every day")
  1270.  
  1271. if __name__ == "__main__":
  1272. # if DEBUG:
  1273. if TESTRUN:
  1274. EPGIMPORTPATH = "H:/Satelite Stuff/epgimport/"
  1275. ENIGMAPATH = "H:/Satelite Stuff/enigma2/"
  1276. PICONSPATH = "H:/Satelite Stuff/picons/"
  1277. CFGPATH = os.path.join(ENIGMAPATH, 'e2m3u2bouquet/')
  1278. sys.exit(main())
Add Comment
Please, Sign In to add comment