Guest User

Untitled

a guest
Nov 7th, 2018
275
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 62.20 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. if (channeldict['stream-url'].endswith('.ts') or channeldict['stream-url'].endswith('.m3u8') or channeldict['stream-url'].find('.') == -1) \
  326. and not channeldict['group-title'].startswith('VOD'):
  327. channeldict['stream-type'] = '4097' if all_iptv_stream_types else '1'
  328. else:
  329. channeldict['group-title'] = u"VOD - {}".format(channeldict['group-title'])
  330. channeldict['stream-type'] = "4097"
  331.  
  332. def parse_map_bouquet_xml(self, dictchannels,providername):
  333. """Check for a mapping override file and parses it if found
  334. """
  335. category_order = []
  336. category_options = {}
  337. mapping_file = self.get_mapping_file(providername)
  338. if mapping_file:
  339. print("\n----Parsing custom bouquet order----")
  340.  
  341. with open(mapping_file, "r") as f:
  342. tree = ElementTree.parse(f)
  343. for node in tree.findall(".//category"):
  344. dictoption = {}
  345.  
  346. category = node.attrib.get('name')
  347. if not type(category) is unicode:
  348. category = category.decode("utf-8")
  349. cat_title_override = node.attrib.get('nameOverride', '')
  350. if not type(cat_title_override) is unicode:
  351. cat_title_override = cat_title_override.decode("utf-8")
  352. dictoption['nameOverride'] = cat_title_override
  353. dictoption['idStart'] = int(node.attrib.get('idStart', '0')) \
  354. if node.attrib.get('idStart', '0').isdigit() else 0
  355. if node.attrib.get('enabled') == 'false':
  356. dictoption["enabled"] = False
  357. # Remove category/bouquet
  358. if category != "VOD":
  359. if category in dictchannels:
  360. dictchannels.pop(category, None)
  361. else:
  362. keys_to_remove = []
  363. for k in dictchannels.iterkeys():
  364. if k.startswith("VOD"):
  365. keys_to_remove.append(k)
  366. if keys_to_remove:
  367. for k in keys_to_remove:
  368. dictchannels.pop(k, None)
  369. else:
  370. dictoption["enabled"] = True
  371. category_order.append(category)
  372.  
  373. category_options[category] = dictoption
  374.  
  375. print("custom bouquet order parsed...")
  376. return category_order, category_options
  377.  
  378. def parse_map_xmltvsources_xml(self,providername):
  379. """Check for a mapping override file and parses it if found
  380. """
  381. list_xmltv_sources = {}
  382. mapping_file = self.get_mapping_file(providername)
  383. if mapping_file:
  384. with open(mapping_file, "r") as f:
  385. tree = ElementTree.parse(f)
  386. for group in tree.findall('.//xmltvextrasources/group'):
  387. group_name = group.attrib.get('id')
  388. urllist = []
  389. for url in group:
  390. urllist.append(url.text)
  391. list_xmltv_sources[group_name] = urllist
  392. return list_xmltv_sources
  393.  
  394. def parse_map_channels_xml(self, dictchannels, xcludesref,providername):
  395. """Check for a mapping override file and applies it if found
  396. """
  397. mappingfile = self.get_mapping_file(providername)
  398. if mappingfile:
  399. print("\n----Parsing custom channel order, please be patient----")
  400.  
  401. with open(mappingfile, "r") as f:
  402. tree = ElementTree.parse(f)
  403. for cat in dictchannels:
  404. if not cat.startswith("VOD"):
  405. # We don't override any individual VOD streams
  406. print("sorting {}".format(cat.encode("utf-8")))
  407.  
  408. sortedchannels = []
  409. listchannels = []
  410. for x in dictchannels[cat]:
  411. listchannels.append(x['stream-name'])
  412. for node in tree.findall(u'.//channel[@category="{}"]'.format(cat)):
  413. sortedchannels.append(node.attrib.get('name'))
  414.  
  415. sortedchannels.extend(listchannels)
  416. # remove duplicates, keep order
  417. listchannels = OrderedDict((x, True) for x in sortedchannels).keys()
  418.  
  419. # sort the channels by new order
  420. channel_order_dict = {channel: index for index, channel in enumerate(listchannels)}
  421. dictchannels[cat].sort(key=lambda x: channel_order_dict[x['stream-name']])
  422.  
  423. for x in dictchannels[cat]:
  424. node = tree.find(u'.//channel[@name="{}"]'.format(x['stream-name']))
  425. if node is not None:
  426. if node.attrib.get('enabled') == 'false':
  427. x['enabled'] = False
  428. x['nameOverride'] = node.attrib.get('nameOverride', '')
  429. # default to current values if attribute doesn't exist
  430. x['tvg-id'] = node.attrib.get('tvg-id', x['tvg-id'])
  431. if node.attrib.get('serviceRef', None) and not xcludesref:
  432. x['serviceRef'] = node.attrib.get('serviceRef', x['serviceRef'])
  433. x['serviceRefOverride'] = True
  434. # streamUrl no longer output to xml file but we still check and process it
  435. x['stream-url'] = node.attrib.get('streamUrl', x['stream-url'])
  436. clear_stream_url = node.attrib.get('clearStreamUrl') == 'true'
  437. if clear_stream_url:
  438. x['stream-url'] = ''
  439.  
  440. print('custom channel order parsed...')
  441.  
  442. def save_map_xml(self, categoryorder, category_options, dictchannels, list_xmltv_sources,providername):
  443. """Create mapping file"""
  444. mappingfile = os.path.join(CFGPATH, self.get_safe_filename(providername) + '-sort-current.xml')
  445. indent = " "
  446. vod_category_output = False
  447.  
  448. if dictchannels:
  449. with open(mappingfile, "wb") as f:
  450. f.write('<!--\r\n')
  451. f.write('{} e2m3u2bouquet Custom mapping file\r\n'.format(indent))
  452. f.write('{} Rearrange bouquets or channels in the order you wish\r\n'.format(indent))
  453. f.write('{} Disable bouquets or channels by setting enabled to "false"\r\n'.format(indent))
  454. f.write('{} Map DVB EPG to IPTV by changing channel serviceRef attribute to match DVB service reference\r\n'.format(indent))
  455. f.write('{} Map XML EPG to different feed by changing channel tvg-id attribute\r\n'.format(indent))
  456. f.write('{} Rename this file as providername-sort-override.xml for changes to apply\r\n'.format(indent))
  457. f.write('-->\r\n')
  458.  
  459. f.write('<mapping>\r\n')
  460.  
  461. f.write('{}<xmltvextrasources>\r\n'.format(indent))
  462. if not list_xmltv_sources:
  463. # output example config
  464. f.write('{}<!-- Example Config\r\n'.format((2 * indent)))
  465. # uk
  466. f.write('{}<group id="{}">\r\n'.format(2 * indent, 'uk'))
  467. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://www.xmltvepg.nl/rytecxmltv-UK.gz'))
  468. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://rytecepg.ipservers.eu/epg_data/rytecxmltv-UK.gz'))
  469. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://rytecepg.wanwizard.eu/rytecxmltv-UK.gz'))
  470. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://91.121.106.172/~rytecepg/epg_data/rytecxmltv-UK.gz'))
  471. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://www.vuplus-community.net/rytec/rytecxmltv-UK.gz'))
  472. f.write('{}</group>\r\n'.format(2 * indent))
  473. # de
  474. f.write('{}<group id="{}">\r\n'.format(2 * indent, 'de'))
  475. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://www.xmltvepg.nl/rytecxmltvGermany.gz'))
  476. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://rytecepg.ipservers.eu/epg_data/rytecxmltvGermany.gz'))
  477. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://rytecepg.wanwizard.eu/rytecxmltvGermany.gz'))
  478. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://91.121.106.172/~rytecepg/epg_data/rytecxmltvGermany.gz'))
  479. f.write('{}<url>{}</url>\r\n'.format(3 * indent, 'http://www.vuplus-community.net/rytec/rytecxmltvGermany.gz'))
  480. f.write('{}</group>\r\n'.format(2 * indent))
  481. f.write('{}-->\r\n'.format(2 * indent))
  482. else:
  483. for group in list_xmltv_sources:
  484. f.write('{}<group id="{}">\r\n'.format(2 * indent, self.xml_escape(group)))
  485. for source in list_xmltv_sources[group]:
  486. f.write('{}<url>{}</url>\r\n'.format(3 * indent, self.xml_escape(source)))
  487. f.write('{}</group>\r\n'.format(2 * indent))
  488. f.write('{}</xmltvextrasources>\r\n'.format(indent))
  489.  
  490. f.write('{}<categories>\r\n'.format(indent))
  491. for cat in categoryorder:
  492. if cat in dictchannels:
  493. if not cat.startswith('VOD -'):
  494. cat_title_override = ''
  495. idStart = ''
  496. if cat in category_options:
  497. cat_title_override = category_options[cat].get('nameOverride', '')
  498. idStart = category_options[cat].get('idStart', '')
  499. f.write('{}<category name="{}" nameOverride="{}" idStart="{}" enabled="true" />\r\n'
  500. .format(2 * indent,
  501. self.xml_escape(cat).encode('utf-8'),
  502. self.xml_escape(cat_title_override).encode('utf-8'),
  503. idStart
  504. ))
  505. elif not vod_category_output:
  506. # Replace multivod categories with single VOD placeholder
  507. cat_title_override = ''
  508. if 'VOD' in category_options:
  509. cat_title_override = category_options['VOD'].get('nameOverride', '')
  510. f.write('{}<category name="{}" nameOverride="{}" enabled="true" />\r\n'
  511. .format(2 * indent,
  512. 'VOD',
  513. self.xml_escape(cat_title_override).encode('utf-8'),
  514. ))
  515. vod_category_output = True
  516. for cat in category_options:
  517. if 'enabled' in category_options[cat] and category_options[cat]['enabled'] is False:
  518. f.write('{}<category name="{}" nameOverride="{}" enabled="false" />\r\n'
  519. .format(2 * indent,
  520. self.xml_escape(cat).encode("utf-8"),
  521. self.xml_escape(cat_title_override).encode("utf-8")
  522. ))
  523.  
  524. f.write('{}</categories>\r\n'.format(indent))
  525.  
  526. f.write('{}<channels>\r\n'.format(indent))
  527. for cat in categoryorder:
  528. if cat in dictchannels:
  529. # Don't output any of the VOD channels
  530. if not cat.startswith('VOD'):
  531. f.write('{}<!-- {} -->\r\n'.format(2 * indent, self.xml_escape(cat.encode('utf-8'))))
  532. for x in dictchannels[cat]:
  533. f.write('{}<channel name="{}" nameOverride="{}" tvg-id="{}" enabled="{}" category="{}" serviceRef="{}" clearStreamUrl="{}" />\r\n'
  534. .format(2 * indent,
  535. self.xml_escape(x['stream-name'].encode('utf-8')),
  536. self.xml_escape(x.get('nameOverride', '').encode('utf-8')),
  537. self.xml_escape(x['tvg-id'].encode('utf-8')),
  538. str(x['enabled']).lower(),
  539. self.xml_escape(cat.encode('utf-8')),
  540. self.xml_escape(x['serviceRef']),
  541. 'false' if x['stream-url'] else 'true'
  542. ))
  543.  
  544. f.write('{}</channels>\r\n'.format(indent))
  545. f.write('</mapping>')
  546.  
  547. def download_picons(self, dictchannels, iconpath):
  548. print('\n----Downloading Picon files, please be patient----')
  549. print('If no Picons exist this will take a few minutes')
  550. if not os.path.isdir(iconpath):
  551. os.makedirs(iconpath)
  552.  
  553. for cat in dictchannels:
  554. if not cat.startswith('VOD'):
  555. # Download Picon if not VOD
  556. for x in dictchannels[cat]:
  557. self.download_picon_file(x['tvg-logo'], self.get_service_title(x), iconpath)
  558. print('\nPicons download completed...')
  559. print('Box will need restarted for Picons to show...')
  560.  
  561. def download_picon_file(self, logourl, title, iconpath):
  562. if logourl:
  563. if not logourl.startswith('http'):
  564. logourl = 'http://{}'.format(logourl)
  565. piconname = self.get_picon_name(title)
  566. piconfilepath = os.path.join(iconpath, piconname)
  567. existingpicon = filter(os.path.isfile, glob.glob(piconfilepath + '*'))
  568.  
  569. if not existingpicon:
  570. if DEBUG:
  571. print("Picon file doesn't exist downloading")
  572. print('PiconURL: {}'.format(logourl))
  573. else:
  574. # Output some kind of progress indicator
  575. sys.stdout.write('.')
  576. sys.stdout.flush()
  577. try:
  578. urllib.urlretrieve(logourl, piconfilepath)
  579. except Exception, e:
  580. if DEBUG:
  581. print(e)
  582. return
  583. self.picon_post_processing(piconfilepath)
  584.  
  585. def picon_post_processing(self, piconfilepath):
  586. """Check type of image received and convert to png
  587. if necessary
  588. """
  589. ext = ""
  590. # get image type
  591. try:
  592. ext = imghdr.what(piconfilepath)
  593. except Exception, e:
  594. if DEBUG:
  595. print(e)
  596. return
  597. # if image but not png convert to png
  598. if (ext is not None) and (ext is not 'png'):
  599. if DEBUG:
  600. print('Converting Picon to png')
  601. try:
  602. Image.open(piconfilepath).save("{}.{}".format(piconfilepath, 'png'))
  603. except Exception, e:
  604. if DEBUG:
  605. print(e)
  606. return
  607. try:
  608. # remove non png file
  609. os.remove(piconfilepath)
  610. except Exception, e:
  611. if DEBUG:
  612. print(e)
  613. return
  614. else:
  615. # rename to correct extension
  616. try:
  617. os.rename(piconfilepath, "{}.{}".format(piconfilepath, ext))
  618. except Exception, e:
  619. if DEBUG:
  620. print(e)
  621. pass
  622.  
  623. def get_picon_name(self, serviceName):
  624. """Convert the service name to a Picon Service Name
  625. """
  626. name = serviceName
  627. if type(name) is unicode:
  628. name = name.encode('utf-8')
  629. name = unicodedata.normalize('NFKD', unicode(name, 'utf_8')).encode('ASCII', 'ignore')
  630. exclude_chars = ['/', '\\', '\'', '"', '`', '?', ' ', '(', ')', ':', '<', '>', '|', '.', '\n', '!']
  631. name = re.sub('[%s]' % ''.join(exclude_chars), '', name)
  632. name = name.replace('&', 'and')
  633. name = name.replace('+', 'plus')
  634. name = name.replace('*', 'star')
  635. name = name.lower()
  636. return name
  637.  
  638. def get_safe_filename(self, filename):
  639. """Convert filename to safe filename
  640. """
  641. name = filename.replace(" ", "_").replace("/", "_")
  642. if type(name) is unicode:
  643. name = name.encode('utf-8')
  644. name = unicodedata.normalize('NFKD', unicode(name, 'utf_8')).encode('ASCII', 'ignore')
  645. exclude_chars = ['/', '\\', '\'', '"', '`',
  646. '?', ' ', '(', ')', ':', '<', '>',
  647. '|', '.', '\n', '!', '&', '+', '*']
  648. name = re.sub('[%s]' % ''.join(exclude_chars), '', name)
  649. name = name.lower()
  650. return name
  651.  
  652. def get_current_bouquet_indexes(self,providername):
  653. """Get the bouquet indexes (ex iptv)
  654. """
  655. current_bouquets_indexes =[]
  656.  
  657. with open(os.path.join(ENIGMAPATH, 'bouquets.tv'), 'r') as f:
  658. for line in f:
  659. if line.startswith('#NAME'):
  660. continue
  661. else:
  662. if not '.suls_iptv_'+self.get_safe_filename(providername) in line:
  663. current_bouquets_indexes.append(line)
  664. return current_bouquets_indexes
  665.  
  666. def create_bouquets(self, category_order, category_options, dictchannels, multivod, allbouquet, bouquettop,providername):
  667. """Create the Enigma2 bouquets
  668. """
  669. print("\n----Creating bouquets----")
  670. # clean old bouquets before writing new
  671. if dictchannels:
  672. for fname in os.listdir(ENIGMAPATH):
  673. if 'userbouquet.suls_iptv_'+self.get_safe_filename(providername) in fname:
  674. os.remove(os.path.join(ENIGMAPATH, fname))
  675. iptv_bouquet_list = []
  676.  
  677. if allbouquet:
  678. iptv_bouquet_list = self.create_all_channels_bouquet(category_order, category_options, dictchannels,providername)
  679.  
  680. vod_categories = list(cat for cat in category_order if cat.startswith('VOD -'))
  681. vod_category_output = False
  682. vod_bouquet_entry_output = False
  683. channel_number_start_offset_output = False
  684.  
  685. for cat in category_order:
  686. if cat in dictchannels:
  687. cat_title = self.get_category_title(cat, category_options)
  688. # create file
  689. cat_filename = self.get_safe_filename(cat_title)
  690.  
  691. if cat in vod_categories and not multivod:
  692. cat_filename = "VOD"
  693.  
  694. bouquet_filepath = os.path.join(ENIGMAPATH, 'userbouquet.suls_iptv_' + self.get_safe_filename(providername) +'_{}.tv'
  695. .format(cat_filename))
  696. if DEBUG:
  697. print("Creating: {}".format(bouquet_filepath))
  698.  
  699. if cat not in vod_categories or multivod:
  700. with open(bouquet_filepath, "w+") as f:
  701. bouquet_name = self.get_safe_filename(providername)+ ' - {}'.format(cat_title).decode("utf-8")
  702. if not cat.startswith('VOD -'):
  703. if cat in category_options and category_options[cat].get('nameOverride', False):
  704. bouquet_name = category_options[cat]['nameOverride'].decode('utf-8')
  705. else:
  706. if 'VOD' in category_options and category_options['VOD'].get('nameOverride', False):
  707. bouquet_name = '{} - {}'\
  708. .format(category_options['VOD']['nameOverride'].decode('utf-8'),
  709. cat_title.replace('VOD - ', '').decode("utf-8"))
  710. channel_num = 0
  711. f.write("#NAME {}\n".format(bouquet_name.encode("utf-8")))
  712. if not channel_number_start_offset_output and not allbouquet:
  713. # write place holder services (for channel numbering)
  714. for i in xrange(100):
  715. f.write('#SERVICE 1:832:d:0:0:0:0:0:0:0:\n')
  716. channel_number_start_offset_output = True
  717. channel_num += 1
  718.  
  719. for x in dictchannels[cat]:
  720. if x['enabled']:
  721. self.save_bouquet_entry(f, x)
  722. channel_num += 1
  723.  
  724. while (channel_num % 100) is not 0:
  725. f.write('#SERVICE 1:832:d:0:0:0:0:0:0:0:\n')
  726. channel_num += 1
  727. elif not vod_category_output and not multivod:
  728. # not multivod - output all the vod services in one file
  729. with open(bouquet_filepath, "w+") as f:
  730. bouquet_name = self.get_safe_filename(providername) + ' - VOD'.decode("utf-8")
  731. if 'VOD' in category_options and category_options['VOD'].get('nameOverride', False):
  732. bouquet_name = category_options['VOD']['nameOverride'].decode('utf-8')
  733.  
  734. channel_num = 0
  735. f.write("#NAME {}\n".format(bouquet_name.encode("utf-8")))
  736. if not channel_number_start_offset_output and not allbouquet:
  737. # write place holder services (for channel numbering)
  738. for i in xrange(100):
  739. f.write('#SERVICE 1:832:d:0:0:0:0:0:0:0:\n')
  740. channel_number_start_offset_output = True
  741. channel_num += 1
  742.  
  743. for vodcat in vod_categories:
  744. if vodcat in dictchannels:
  745. # Insert group description placeholder in bouquet
  746. f.write("#SERVICE 1:64:0:0:0:0:0:0:0:0:\n")
  747. f.write("#DESCRIPTION {}\n". format(vodcat))
  748. for x in dictchannels[vodcat]:
  749. self.save_bouquet_entry(f, x)
  750. channel_num += 1
  751.  
  752. while (channel_num % 100) is not 0:
  753. f.write('#SERVICE 1:832:d:0:0:0:0:0:0:0:\n')
  754. channel_num += 1
  755. vod_category_output = True
  756.  
  757. # Add to bouquet index list
  758. if cat not in vod_categories or (cat in vod_categories and not vod_bouquet_entry_output):
  759. iptv_bouquet_list.append(self.get_bouquet_index_name(cat_filename,self.get_safe_filename(providername)))
  760. if cat in vod_categories and not multivod:
  761. vod_bouquet_entry_output = True
  762.  
  763. # write the bouquets.tv indexes
  764. self.save_bouquet_index_entries(iptv_bouquet_list, bouquettop,providername)
  765.  
  766. print("bouquets created ...")
  767.  
  768. def create_all_channels_bouquet(self, category_order, category_options, dictchannels,providername):
  769. """Create the Enigma2 all channels bouquet
  770. """
  771. print("\n----Creating all channels bouquet----")
  772.  
  773. bouquet_indexes = []
  774.  
  775. vod_categories = list(cat for cat in category_order if cat.startswith('VOD -'))
  776. bouquet_name = self.get_safe_filename(providername) + "All Channels"
  777. cat_filename = self.get_safe_filename(bouquet_name)
  778.  
  779. # create file
  780. bouquet_filepath = os.path.join(ENIGMAPATH, 'userbouquet.suls_iptv_' + self.get_safe_filename(providername) + '_{}.tv'
  781. .format(cat_filename))
  782. if DEBUG:
  783. print("Creating: {}".format(bouquet_filepath))
  784.  
  785. with open(bouquet_filepath, "w+") as f:
  786. f.write("#NAME " + providername + " - {}\n".format(bouquet_name.encode("utf-8")))
  787.  
  788. # write place holder channels (for channel numbering)
  789. for i in xrange(100):
  790. f.write('#SERVICE 1:832:d:0:0:0:0:0:0:0:\n')
  791. channel_num = 1
  792.  
  793. for cat in category_order:
  794. if cat in dictchannels:
  795. if cat not in vod_categories:
  796. cat_title = self.get_category_title(cat, category_options)
  797. # Insert group description placeholder in bouquet
  798. f.write("#SERVICE 1:64:0:0:0:0:0:0:0:0:\n")
  799. f.write("#DESCRIPTION {}\n".format(cat_title))
  800. for x in dictchannels[cat]:
  801. if x['enabled']:
  802. self.save_bouquet_entry(f, x)
  803. channel_num += 1
  804.  
  805. while (channel_num % 100) is not 0:
  806. f.write('#SERVICE 1:832:d:0:0:0:0:0:0:0:\n')
  807. channel_num += 1
  808.  
  809. # Add to bouquet index list
  810. bouquet_indexes.append(self.get_bouquet_index_name(cat_filename,self.get_safe_filename(providername)))
  811. print("all channels bouquet created ...")
  812. return bouquet_indexes
  813.  
  814. def save_bouquet_entry(self, f, channel):
  815. """Add service to bouquet file
  816. """
  817. f.write("#SERVICE {}:{}:{}\n"
  818. .format(channel['serviceRef'], channel['stream-url']
  819. .replace(":", "%3a"), self.get_service_title(channel).encode("utf-8")))
  820. f.write("#DESCRIPTION {}\n".format(self.get_service_title(channel).encode("utf-8")))
  821.  
  822. def get_bouquet_index_name(self, filename,providername):
  823. 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'
  824. .format(filename))
  825.  
  826. def save_bouquet_index_entries(self, iptv_bouquets, bouquettop,providername):
  827. """Add to the main bouquets.tv file
  828. """
  829. # get current bouquets indexes
  830. current_bouquet_indexes = self.get_current_bouquet_indexes(providername)
  831.  
  832. if iptv_bouquets:
  833. with open(os.path.join(ENIGMAPATH, 'bouquets.tv'), 'w') as f:
  834. f.write('#NAME Bouquets (TV)\n')
  835. if bouquettop:
  836. for bouquet in iptv_bouquets:
  837. f.write(bouquet)
  838. for bouquet in current_bouquet_indexes:
  839. f.write(bouquet)
  840. else:
  841. for bouquet in current_bouquet_indexes:
  842. f.write(bouquet)
  843. for bouquet in iptv_bouquets:
  844. f.write(bouquet)
  845.  
  846. def reload_bouquets(self):
  847. if not TESTRUN:
  848. print("\n----Reloading bouquets----")
  849. os.system("wget -qO - http://127.0.0.1/web/servicelistreload?mode=2 > /dev/null 2>&1 &")
  850. print("bouquets reloaded...")
  851.  
  852. def create_epgimporter_config(self, categoryorder, category_options, dictchannels, list_xmltv_sources, epgurl, provider):
  853. indent = " "
  854. if DEBUG:
  855. print("creating EPGImporter config")
  856. # create channels file
  857. if not os.path.isdir(EPGIMPORTPATH):
  858. os.makedirs(EPGIMPORTPATH)
  859. channels_filename = os.path.join(EPGIMPORTPATH, 'suls_iptv_' + self.get_safe_filename(provider) + '_channels.xml')
  860.  
  861. if dictchannels:
  862. with open(channels_filename, "w+") as f:
  863. f.write('<channels>\n')
  864. for cat in categoryorder:
  865. if cat in dictchannels:
  866. if not cat.startswith('VOD'):
  867. cat_title = self.get_category_title(cat, category_options)
  868.  
  869. f.write('{}<!-- {} -->\n'.format(indent, self.xml_escape(cat_title.encode('utf-8'))))
  870. for x in dictchannels[cat]:
  871. tvg_id = x['tvg-id'] if x['tvg-id'] else self.get_service_title(x)
  872. if x['enabled']:
  873. f.write('{}<channel id="{}">{}:http%3a//example.m3u8</channel> <!-- {} -->\n'
  874. .format(indent, self.xml_escape(tvg_id.encode('utf-8')), x['serviceRef'],
  875. self.xml_escape(self.get_service_title(x).encode('utf-8'))))
  876. f.write("</channels>\n")
  877.  
  878. # create epg-importer sources file for providers feed
  879. self.create_epgimport_source([epgurl], provider)
  880.  
  881. # create epg-importer sources file for additional feeds
  882. for group in list_xmltv_sources:
  883. self.create_epgimport_source(list_xmltv_sources[group], '{} - {}'.format(provider, group),provider)
  884.  
  885. def create_epgimport_source(self, sources, source_name):
  886. """Create epg-importer source file
  887. """
  888. indent = " "
  889. channels_filename = os.path.join(EPGIMPORTPATH, 'suls_iptv_' + self.get_safe_filename(source_name) + '_channels.xml')
  890.  
  891. # write providers epg feed
  892. source_filename = os.path.join(EPGIMPORTPATH, 'suls_iptv_{}.sources.xml'
  893. .format(self.get_safe_filename(source_name)))
  894.  
  895. with open(os.path.join(EPGIMPORTPATH, source_filename), "w+") as f:
  896. f.write('<sources>\n')
  897. f.write('{}<sourcecat sourcecatname="IPTV Bouquet Maker - E2m3u2bouquet">\n'.format(indent))
  898. f.write('{}<source type="gen_xmltv" channels="{}">\n'
  899. .format(2 * indent, channels_filename))
  900. f.write('{}<description>{}</description>\n'.format(3 * indent, self.xml_escape(source_name)))
  901. for source in sources:
  902. f.write('{}<url>{}</url>\n'.format(3 * indent, self.xml_escape(source)))
  903. f.write('{}</source>\n'.format(2 * indent))
  904. f.write('{}</sourcecat>\n'.format(indent))
  905. f.write('</sources>\n')
  906.  
  907. def read_providers(self,providerfile):
  908. # Check we have data
  909. try:
  910. if not os.path.getsize(providerfile):
  911. raise Exception('Providers file is empty')
  912. except Exception, e:
  913. raise e
  914. with open(providerfile, "r") as f:
  915. for line in f:
  916. if line == "400: Invalid request\n":
  917. print("Providers download is invalid please resolve or use URL based setup")
  918. sys(exit(1))
  919. line = base64.b64decode(line)
  920. if line:
  921. provider = {
  922. 'name': line.split(',')[0],
  923. 'm3u': line.split(',')[1],
  924. 'epg': line.split(',')[2]
  925. }
  926. PROVIDERS[provider['name']] = provider
  927.  
  928. if not DEBUG:
  929. # remove providers file
  930. os.remove(providerfile)
  931. return PROVIDERS
  932.  
  933. def process_provider(self, provider, username, password):
  934. supported_providers = ""
  935. for line in PROVIDERS:
  936. supported_providers += " " + PROVIDERS[line]['name']
  937. if PROVIDERS[line]['name'].upper() == provider.upper():
  938. if DEBUG:
  939. print("----Provider setup details----")
  940. print("m3u = " + PROVIDERS[line]['m3u'].replace("USERNAME", username).replace("PASSWORD", password))
  941. print("epg = " + PROVIDERS[line]['epg'].replace("USERNAME", username).replace("PASSWORD", password) + "\n")
  942. return PROVIDERS[line]['m3u'].replace("USERNAME", username).replace("PASSWORD", password), \
  943. PROVIDERS[line]['epg'].replace("USERNAME", username).replace("PASSWORD", password), \
  944. supported_providers
  945. # If we get here the supplied provider is invalid
  946. return "NOTFOUND", "", 0, 0, 0, 0, supported_providers
  947.  
  948. def get_mapping_file(self,providername):
  949. mapping_file = None
  950. search_path = [os.path.join(CFGPATH, self.get_safe_filename(providername) + '-sort-override.xml'),
  951. os.path.join(os.getcwd(), self.get_safe_filename(providername) + '-sort-override.xml')]
  952. for path in search_path:
  953. if os.path.isfile(path):
  954. mapping_file = path
  955. break;
  956. return mapping_file
  957.  
  958. def xml_escape(self, string):
  959. return string.replace("&", "&amp;") \
  960. .replace("\"", "&quot;") \
  961. .replace("'", "&apos;") \
  962. .replace("<", "&lt;") \
  963. .replace(">", "&gt;")
  964.  
  965. def xml_unescape(self, string):
  966. return string.replace('&quot;', '"') \
  967. .replace() \
  968. .replace("&apos;", "'") \
  969. .replace("&lt;", "<") \
  970. .replace("&gt;", ">") \
  971. .replace("&amp;", "&")
  972.  
  973. def get_service_title(self, channel):
  974. """Return the title override if set else the title
  975. """
  976. return channel['nameOverride'] if channel.get('nameOverride', False) else channel['stream-name']
  977.  
  978. def get_category_title(self, cat, category_options):
  979. """Return the title override if set else the title
  980. """
  981. if cat in category_options:
  982. return category_options[cat]['nameOverride'] if category_options[cat].get('nameOverride', False) else cat
  983. return cat
  984.  
  985. def get_category_id(self, cat,providername):
  986. """Generate 32 bit category id to help make service refs unique"""
  987. return hashlib.md5(providername+ cat).hexdigest()[:8]
  988.  
  989. class config:
  990.  
  991. def makeconfig(self,configfile):
  992. print('Default configuration file created in '+ CFGPATH + 'config.xml\n')
  993.  
  994. f= open(configfile, "w+")
  995. f.write("""<!-- E2m3u2bouquet supplier config file -->
  996. <!-- Add as many suppliers as required and run the script with no parameters -->
  997. <!-- this config file will be used and the relevant bouquets set up for all suppliers entered -->
  998. <!-- 0 = No/false -->
  999. <!-- 1 = Yes/true -->
  1000. <config>
  1001. <supplier>
  1002. <name>Supplier Name</name> <!-- Supplier Name -->
  1003. <enabled>1</enabled> <!-- Enable or disable the supplier (0 or 1) -->
  1004. <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 -->
  1005. <epgurl><![CDATA[http://address.yourprovider.com:80/xmltv.php?username=YOURUSERNAME&password=YOURPASSWORD]]></epgurl> <!-- XML EPG URL including your username and password -->
  1006. <iptvtypes>0</iptvtypes> <!-- Change all streams to IPTV type (0 or 1) -->
  1007. <multivod>0</multivod> <!-- Split VOD into seperate categories (0 or 1) -->
  1008. <allbouquet>1</allbouquet> <!-- Create all channels bouquet -->
  1009. <picons>0</picons> <!-- Automatically download Picons (0 or 1) -->
  1010. <iconpath>/usr/share/enigma2/picon/</iconpath> <!-- Location to store picons -->
  1011. <xcludesref>0</xcludesref> <!-- Disable service ref overriding from override.xml file (0 or 1) -->
  1012. <bouqueturl></bouqueturl> <!-- URL to download providers bouquet - to map custom service references -->
  1013. <bouquetdownload>0</bouquetdownload> <!-- Download providers bouquet (use default url) - to map custom service references -->
  1014. <bouquettop>0</bouquettop> <!-- Place IPTV bouquets at top (0 or 1)-->
  1015. </supplier>
  1016. <supplier>
  1017. <name>Supplier Name</name> <!-- Supplier Name -->
  1018. <enabled>0</enabled> <!-- Enable or disable the supplier (0 or 1) -->
  1019. <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 -->
  1020. <epgurl><![CDATA[http://address.yourprovider.com:80/xmltv.php?username=YOURUSERNAME&password=YOURPASSWORD]]></epgurl> <!-- XML EPG URL including your username and password -->
  1021. <iptvtypes>0</iptvtypes> <!-- Change all streams to IPTV type (0 or 1) -->
  1022. <multivod>0</multivod> <!-- Split VOD into seperate categories (0 or 1) -->
  1023. <allbouquet>1</allbouquet> <!-- Create all channels bouquet -->
  1024. <picons>0</picons> <!-- Automatically download Picons (0 or 1) -->
  1025. <iconpath>/usr/share/enigma2/picon/</iconpath> <!-- Location to store picons -->
  1026. <xcludesref>0</xcludesref> <!-- Disable service ref overriding from override.xml file (0 or 1) -->
  1027. <bouqueturl></bouqueturl> <!-- URL to download providers bouquet - to map custom service references -->
  1028. <bouquetdownload>0</bouquetdownload> <!-- Download providers bouquet (use default url) - to map custom service references -->
  1029. <bouquettop>0</bouquettop> <!-- Place IPTV bouquets at top (0 or 1)-->
  1030. </supplier>
  1031. </config>""")
  1032.  
  1033. def readconfig(self,configfile):
  1034. suppliers = []
  1035. with open(configfile, "r") as f:
  1036. tree = ElementTree.parse(f)
  1037. for node in tree.findall(".//supplier"):
  1038. supplier = {}
  1039. for child in node:
  1040. if (DEBUG == 1) or (TESTRUN == 1):
  1041. print(child.tag + " = " + str("" if child.text is None else child.text))
  1042. supplier[child.tag] = str("" if child.text is None else child.text)
  1043. suppliers.append(supplier)
  1044. return suppliers
  1045.  
  1046. def run_e2m3u2bouquet(self,provider):
  1047. # Build up our args
  1048. newargs = []
  1049. newargs.append('-n={}'.format(provider["name"]))
  1050. newargs.append('-m={}'.format(provider["m3uurl"]))
  1051. newargs.append('-e={}'.format(provider["epgurl"]))
  1052. if provider["iptvtypes"]=="1":
  1053. newargs.append('-i')
  1054. if provider["multivod"]=="1":
  1055. newargs.append('-M')
  1056. if provider["allbouquet"]=="1":
  1057. newargs.append('-a')
  1058. if provider["picons"]=="1":
  1059. newargs.append('-P')
  1060. newargs.append('-q={}'.format(provider["iconpath"]))
  1061. if provider["xcludesref"]=="1":
  1062. newargs.append('-xs')
  1063. if provider["bouquettop"]=="1":
  1064. newargs.append('-bt')
  1065. if provider["bouquetdownload"]=="1":
  1066. newargs.append('-bd')
  1067. newargs.append('-b={}'.format(provider["bouqueturl"]))
  1068. # Recall ourselves
  1069. main(newargs)
  1070.  
  1071. def main(argv=None): # IGNORE:C0111
  1072. # Command line options.
  1073. if argv is None:
  1074. argv = sys.argv
  1075. else:
  1076. sys.argv.extend(argv)
  1077. program_name = os.path.basename(sys.argv[0])
  1078. program_version = "v%s" % __version__
  1079. program_build_date = str(__updated__)
  1080. program_version_message = '%(prog)s {} ({})'.format(program_version, program_build_date)
  1081. program_shortdesc = __doc__.split("\n")[1]
  1082. program_license = """{}
  1083.  
  1084. Copyright 2017. All rights reserved.
  1085. Created on {}.
  1086. Licensed under GNU GENERAL PUBLIC LICENSE version 3
  1087. Distributed on an "AS IS" basis without warranties
  1088. or conditions of any kind, either express or implied.
  1089.  
  1090. USAGE
  1091. """.format(program_shortdesc, str(__date__))
  1092.  
  1093. try:
  1094. # Setup argument parser
  1095. parser = ArgumentParser(description=program_license, formatter_class=RawDescriptionHelpFormatter)
  1096. # URL Based Setup
  1097. urlgroup = parser.add_argument_group('URL Based Setup')
  1098. urlgroup.add_argument('-m', '--m3uurl', dest='m3uurl', action='store',
  1099. help='URL to download m3u data from (required)')
  1100. urlgroup.add_argument('-e', '--epgurl', dest='epgurl', action='store',
  1101. help='URL source for XML TV epg data sources')
  1102. # Provider based setup
  1103. providergroup = parser.add_argument_group('Provider Based Setup')
  1104. providergroup.add_argument('-n', '--providername', dest='providername', action='store',
  1105. help='Host IPTV provider name (e.g. FAB/EPIC) (required)')
  1106. providergroup.add_argument('-u', '--username', dest='username', action='store',
  1107. help='Your IPTV username (required)')
  1108. providergroup.add_argument('-p', '--password', dest='password', action='store',
  1109. help='Your IPTV password (required)')
  1110. # Options
  1111. parser.add_argument('-i', '--iptvtypes', dest='iptvtypes', action='store_true',
  1112. help='Treat all stream references as IPTV stream type. (required for some enigma boxes)')
  1113. parser.add_argument('-M', '--multivod', dest='multivod', action='store_true',
  1114. help='Create multiple VOD bouquets rather single VOD bouquet')
  1115. parser.add_argument('-a', '--allbouquet', dest='allbouquet', action='store_true',
  1116. help='Create all channels bouquet')
  1117. parser.add_argument('-P', '--picons', dest='picons', action='store_true',
  1118. help='Automatically download of Picons, this option will slow the execution')
  1119. parser.add_argument('-q', '--iconpath', dest='iconpath', action='store',
  1120. help='Option path to store picons, if not supplied defaults to /usr/share/enigma2/picon/')
  1121. parser.add_argument('-xs', '--xcludesref', dest='xcludesref', action='store_true',
  1122. help='Disable service ref overriding from override.xml file')
  1123. parser.add_argument('-b', '--bouqueturl', dest='bouqueturl', action='store',
  1124. help='URL to download providers bouquet - to map custom service references')
  1125. parser.add_argument('-bd', '--bouquetdownload', dest='bouquetdownload', action='store_true',
  1126. help='Download providers bouquet (use default url) - to map custom service references')
  1127. parser.add_argument('-bt', '--bouquettop', dest='bouquettop', action='store_true',
  1128. help='Place IPTV bouquets at top')
  1129. parser.add_argument('-U', '--uninstall', dest='uninstall', action='store_true',
  1130. help='Uninstall all changes made by this script')
  1131. parser.add_argument('-V', '--version', action='version', version=program_version_message)
  1132.  
  1133. # Process arguments
  1134. args = parser.parse_args()
  1135. m3uurl = args.m3uurl
  1136. epgurl = args.epgurl
  1137. iptvtypes = args.iptvtypes
  1138. uninstall = args.uninstall
  1139. multivod = args.multivod
  1140. allbouquet = args.allbouquet
  1141. bouquet_url = args.bouqueturl
  1142. bouquet_download = args.bouquetdownload
  1143. picons = args.picons
  1144. iconpath = args.iconpath
  1145. xcludesref = args.xcludesref
  1146. bouquettop = args.bouquettop
  1147. provider = args.providername
  1148. username = args.username
  1149. password = args.password
  1150. # Set epg to rytec if nothing else provided
  1151. if epgurl is None:
  1152. epgurl = "http://www.vuplus-community.net/rytec/rytecxmltv-UK.gz"
  1153. # Set piconpath
  1154. if iconpath is None:
  1155. iconpath = PICONSPATH
  1156. if provider is None:
  1157. provider = "E2m3u2Bouquet"
  1158. # Check we have enough to proceed
  1159. if (m3uurl is None) and ((provider is None) or (username is None) or (password is None)) and uninstall is False:
  1160. print('\n********************************')
  1161. print('E2m3u2bouquet - Config based setup')
  1162. print("********************************\n")
  1163. configs = config()
  1164. if os.path.isfile(CFGPATH+"config.xml"):
  1165. supplierslist = configs.readconfig(CFGPATH+"config.xml")
  1166. for supplier in supplierslist:
  1167. if supplier["enabled"]=="1":
  1168. if supplier["name"]=="Supplier Name":
  1169. print("Please enter your details in the supplier config file in - " + CFGPATH+"config.xml")
  1170. sys.exit(2)
  1171. else:
  1172. print('\n********************************')
  1173. print('Config based setup - '+ supplier["name"])
  1174. print("********************************\n")
  1175. configs.run_e2m3u2bouquet(supplier)
  1176. else:
  1177. print("\nSupplier: "+supplier["name"] + " is disabled - skipping.........\n")
  1178. sys.exit(0)
  1179. else:
  1180. configs.makeconfig(CFGPATH+"config.xml")
  1181. print('Please ensure correct command line options are passed to the program \n'
  1182. 'or populate the config file in ' + CFGPATH + "config.xml \n" +
  1183. 'for help use --help\n')
  1184. parser.print_usage()
  1185. sys.exit(1)
  1186.  
  1187. except KeyboardInterrupt:
  1188. ### handle keyboard interrupt ###
  1189. return 0
  1190.  
  1191. except Exception, e:
  1192. if DEBUG or TESTRUN:
  1193. raise e
  1194. indent = len(program_name) * " "
  1195. sys.stderr.write(program_name + ": " + repr(e) + "\n")
  1196. sys.stderr.write(indent + " for help use --help")
  1197. return 2
  1198.  
  1199. # # Core program logic starts here
  1200. urllib._urlopener = AppUrlOpener()
  1201. e2m3uSetup = IPTVSetup()
  1202. if uninstall:
  1203. # Clean up any existing files
  1204. e2m3uSetup.uninstaller()
  1205. # reload bouquets
  1206. e2m3uSetup.reload_bouquets()
  1207. print("Uninstall only, program exiting ...")
  1208. sys.exit(1) # Quit here if we just want to uninstall
  1209. else:
  1210. # create config folder if it doesn't exist
  1211. if not os.path.isdir(CFGPATH):
  1212. os.makedirs(CFGPATH)
  1213.  
  1214. # Work out provider based setup if that's what we have
  1215. if (provider is not None) and (username is not None) or (password is not None):
  1216. providersfile = e2m3uSetup.download_providers(PROVIDERSURL)
  1217. e2m3uSetup.read_providers(providersfile)
  1218. m3uurl, epgurl, supported_providers = e2m3uSetup.process_provider(
  1219. provider, username, password)
  1220. if m3uurl == "NOTFOUND":
  1221. print("----ERROR----")
  1222. print("Provider not found, supported providers = " + supported_providers)
  1223. sys(exit(1))
  1224.  
  1225. # get default provider bouquet download url if bouquet download set and no bouquet url given
  1226. if bouquet_download and not bouquet_url:
  1227. # set bouquet_url to default url
  1228. pos = m3uurl.find('get.php')
  1229. if pos != -1:
  1230. bouquet_url = m3uurl[0:pos + 7] + '?username={}&password={}&type=dreambox&output=ts'.format(
  1231. username, password)
  1232. # Download panel bouquet
  1233. panel_bouquet = None
  1234. if bouquet_url:
  1235. panel_bouquet_file = e2m3uSetup.download_bouquet(bouquet_url)
  1236. panel_bouquet = e2m3uSetup.parse_panel_bouquet(panel_bouquet_file)
  1237. # Download m3u
  1238. m3ufile = e2m3uSetup.download_m3u(m3uurl)
  1239. # parse m3u file
  1240. categoryorder, category_options, dictchannels = e2m3uSetup.parse_m3u(m3ufile, iptvtypes, panel_bouquet,
  1241. xcludesref,provider)
  1242. list_xmltv_sources = e2m3uSetup.parse_map_xmltvsources_xml(provider)
  1243. # save xml mapping - should be after m3u parsing
  1244. e2m3uSetup.save_map_xml(categoryorder, category_options, dictchannels, list_xmltv_sources,provider)
  1245.  
  1246. #download picons
  1247. if picons:
  1248. e2m3uSetup.download_picons(dictchannels, iconpath)
  1249. # Create bouquet files
  1250. e2m3uSetup.create_bouquets(categoryorder, category_options, dictchannels, multivod, allbouquet, bouquettop,provider)
  1251. # Now create custom channels for each bouquet
  1252. print("\n----Creating EPG-Importer config ----")
  1253. e2m3uSetup.create_epgimporter_config(categoryorder, category_options, dictchannels, list_xmltv_sources, epgurl, provider)
  1254. print("EPG-Importer config created...")
  1255. # reload bouquets
  1256. e2m3uSetup.reload_bouquets()
  1257. print("\n********************************")
  1258. print("Engima2 IPTV bouquets created ! ")
  1259. print("********************************")
  1260. print("\nTo enable EPG data")
  1261. print("Please open EPG-Importer plugin.. ")
  1262. print("Select sources and enable the new IPTV source")
  1263. print("(will be listed as {} under 'IPTV Bouquet Maker - E2m3u2bouquet')".format(provider))
  1264. print("Save the selected sources, press yellow button to start manual import")
  1265. print("You can then set EPG-Importer to automatically import the EPG every day")
  1266.  
  1267. if __name__ == "__main__":
  1268. # if DEBUG:
  1269. if TESTRUN:
  1270. EPGIMPORTPATH = "H:/Satelite Stuff/epgimport/"
  1271. ENIGMAPATH = "H:/Satelite Stuff/enigma2/"
  1272. PICONSPATH = "H:/Satelite Stuff/picons/"
  1273. CFGPATH = os.path.join(ENIGMAPATH, 'e2m3u2bouquet/')
  1274. sys.exit(main())
Add Comment
Please, Sign In to add comment