fork(1) download
  1. #!/usr/bin/python
  2. # encoding: utf-8
  3. # Change notes
  4. # v0.1 - Initial version (Dave Sully)
  5. # v0.2 - Updated to use providers epg, doesn't need reboot to take effect - so could be scheduled from cron job (Doug MacKay)
  6. # v0.3 - Complete restructure of the code base to some thing more usable going forward, incorporated Dougs changes to EPG data source (Dave Sully)
  7. # - tvg-id now included in the channels
  8. # - better parsing of m3u data (Doug MacKay)
  9. # - downloads m3u file from url
  10. # - sets custom source to providers xml tv feed (as per Dougs v0.2)
  11. # - fixed IPTV streams not playing / having incorrect stream type
  12. # - option to make all streams IPTV type
  13. # - option to split VOD bouquets by initial category
  14. # - all paramters arg based so in theory works for other providers and can be croned
  15. # - auto reloads bouquets (Doug MacKay)
  16. # - debug \ testrun modes
  17.  
  18. '''
  19. e2m3u2bouquet.e2m3u2bouquet -- Enigma2 IPTV m3u to bouquet parser
  20.  
  21. @author: Dave Sully Doug MacKay
  22. @copyright: 2017 All rights reserved.
  23. @license: http://w...content-available-to-author-only...e.org/licenses/LICENSE-2.0
  24. @contact: me@you.com
  25. @deffield updated: Updated
  26. '''
  27. import sys
  28. import os
  29.  
  30. from argparse import ArgumentParser
  31. from argparse import RawDescriptionHelpFormatter
  32. #from crontab import CronTab
  33.  
  34. __all__ = []
  35. __version__ = 0.3
  36. __date__ = '2017-06-04'
  37. __updated__ = '2017-06-04'
  38.  
  39. DEBUG = 0
  40. TESTRUN = 0
  41.  
  42. ENIGMAPATH = "/etc/enigma2/"
  43. EPGIMPORTPATH = "/etc/epgimport/"
  44.  
  45. class CLIError(Exception):
  46. '''Generic exception to raise and log different fatal errors.'''
  47. def __init__(self, msg):
  48. super(CLIError).__init__(type(self))
  49. self.msg = "E: %s" % msg
  50. def __str__(self):
  51. return self.msg
  52. def __unicode__(self):
  53. return self.msg
  54.  
  55. # Clean up routine to remove any previously made changes
  56. def uninstaller():
  57. print("----Running uninstall----")
  58. try:
  59. # m3u file
  60. print("Removing old m3u files...")
  61. if os.path.isfile(os.getcwd()+"/e2m3u2bouquet.m3u"):
  62. os.remove(os.getcwd()+"/e2m3u2bouquet.m3u")
  63. # Bouquets
  64. print("Removing old IPTV bouquets...")
  65. for fname in os.listdir(ENIGMAPATH):
  66. if "userbouquet.suls_iptv_" in fname:
  67. os.remove(ENIGMAPATH+fname)
  68. elif "bouquets.tv.bak" in fname:
  69. os.remove(ENIGMAPATH+fname)
  70. # Custom Channels and sources
  71. print("Removing IPTV custom channels...")
  72. for fname in os.listdir(EPGIMPORTPATH):
  73. if "suls_iptv_" in fname:
  74. os.remove(EPGIMPORTPATH+fname)
  75. # bouquets.tv
  76. print("Removing IPTV bouquets from bouquets.tv...")
  77. os.rename(ENIGMAPATH + "bouquets.tv",ENIGMAPATH + "bouquets.tv.bak")
  78. tvfile = open(ENIGMAPATH + "bouquets.tv","w+")
  79. bakfile = open(ENIGMAPATH + "bouquets.tv.bak")
  80. for line in bakfile:
  81. if ".suls_iptv_" not in line:
  82. tvfile.write(line)
  83. bakfile.close()
  84. tvfile.close()
  85. except Exception, e:
  86. raise(e)
  87. print("----Uninstall complete----")
  88.  
  89. # Download m3u file from url
  90. def download_m3u(url):
  91. import urllib
  92. print("\n----Downloading m3u file----")
  93. if DEBUG:
  94. print("m3uurl="+url)
  95. try:
  96. webFile = urllib.urlopen(url)
  97. localFile = open("e2m3u2bouquet.m3u", 'w')
  98. localFile.write(webFile.read())
  99. webFile.close()
  100. except Exception, e:
  101. raise(e)
  102. print("file saved as "+os.getcwd()+"/e2m3u2bouquet.m3u")
  103. return os.getcwd()+"/e2m3u2bouquet.m3u"
  104.  
  105. # core parsing routine
  106. def parsem3u(filename,all_iptv_stream_types,multivod):
  107. # Extract and generate the following items from the m3u
  108. #0 category
  109. #1 title
  110. #2 tvg-id
  111. #3 stream url
  112. #4 stream type
  113. #5 channel ID
  114. print("\n----Parsing m3u file----")
  115. listchannels=[]
  116. with open (filename, "r") as myfile:
  117. for line in myfile:
  118. if 'EXTM3U' in line: # First line we are not interested
  119. continue
  120. elif 'EXTINF:' in line: # Info line - work out group and output the line
  121. channels = [line.split('"')[7],(line.split('"')[8])[1:].strip(),line.split('"')[1]]
  122. elif 'http:' in line:
  123. channels.append(line.strip())
  124. listchannels.append(channels)
  125. # Clean up VOD and create stream types
  126. for x in listchannels:
  127. if x[3].endswith(('.mp4','mkv','.avi',"mpg")):
  128. if multivod:
  129. x[0] = "VOD - "+x[0]
  130. else:
  131. x[0] = "VOD"
  132. x.append("4097")
  133. elif all_iptv_stream_types:
  134. x.append("4097")
  135. else:
  136. x.append("1")
  137. # Sort the list
  138. listchannels.sort()
  139. # Add Service references
  140. num =1
  141. for x in listchannels:
  142. x.append(x[4]+":0:1:"+str(num)+":0:0:0:0:0:0")
  143. num += 1
  144. # Have a look at what we have
  145. if DEBUG and TESTRUN:
  146. datafile = open("channels.debug","w+")
  147. for line in listchannels:
  148. datafile.write(":".join(line)+"\n")
  149. datafile.close()
  150. print("Completed parsing data...")
  151. return listchannels
  152.  
  153. def create_bouquets(listchannels):
  154. print("\n----Creating bouquets----")
  155. for x in listchannels:
  156. # Create file if does exits
  157. if not os.path.isfile(ENIGMAPATH + "userbouquet.suls_iptv_"+x[0].replace(" ","_").replace("/","_")+".tv"):
  158. #create file
  159. if DEBUG:
  160. print("Creating: "+ENIGMAPATH + "userbouquet.suls_iptv_"+x[0].replace(" ","_").replace("/","_")+".tv")
  161. bouquetfile = open(ENIGMAPATH + "userbouquet.suls_iptv_"+x[0].replace(" ","_").replace("/","_")+".tv","w+")
  162. bouquetfile.write("#NAME IPTV - "+x[0].replace("/"," ")+"\n")
  163. bouquetfile.write("#SERVICE "+x[5]+":"+x[3].replace(":","%3a")+":"+x[2]+"\n")
  164. bouquetfile.write("#DESCRIPTION "+x[1]+"\n")
  165. bouquetfile.close()
  166. # Add to main bouquets files
  167. tvfile = open(ENIGMAPATH + "bouquets.tv","a")
  168. tvfile.write("#SERVICE 1:7:1:0:0:0:0:0:0:0:FROM BOUQUET \"userbouquet.suls_iptv_"+x[0].replace(" ","_").replace("/","_")+".tv\" ORDER BY bouquet\n")
  169. tvfile.close()
  170. else:
  171. #Append to file
  172. bouquetfile = open(ENIGMAPATH + "userbouquet.suls_iptv_"+x[0].replace(" ","_").replace("/","_")+".tv","a")
  173. bouquetfile.write("#SERVICE "+x[5]+":"+x[3].replace(":","%3a")+":"+x[2]+"\n")
  174. bouquetfile.write("#DESCRIPTION "+x[1]+"\n")
  175. bouquetfile.close()
  176. print("bouquets created ...")
  177.  
  178. def reloadBouquets():
  179. if not TESTRUN:
  180. print("\n----Reloading bouquets----")
  181. os.system("wget -qO - http://127.0.0.1/web/servicelistreload?mode=2 > /dev/null 2>&1 &")
  182. print("bouquets reloaded...")
  183.  
  184. def create_custom_channel(bouquet,listchannels):
  185. if DEBUG:
  186. print("creating custom channel - " + bouquet)
  187. #create channels file and opening tag
  188. channelfile = open(EPGIMPORTPATH + "suls_iptv_"+bouquet.replace(" ","_").replace("/","_")+".channels.xml","w+")
  189. channelfile.write("<channels>\n")
  190. # loop through list out putting matching stuff
  191. for x in listchannels:
  192. if x[0] == bouquet:
  193. # now using tvg-id from the mm3u file rather than dodgy cleaning attempts
  194. channelfile.write("<channel id=\""+x[2].replace("&","&amp;")+"\">"+x[5]+":http%3a//example.m3u8</channel> <!-- "+x[1]+" -->\n")
  195. # Write closing tag and close file
  196. channelfile.write("</channels>\n")
  197. channelfile.close()
  198.  
  199. def create_custom_source(bouquet,epgurl):
  200. if DEBUG:
  201. print("creating custom source - " + bouquet)
  202. #create custom sources file
  203. sourcefile = open(EPGIMPORTPATH + "suls_iptv_"+bouquet.replace(" ","_").replace("/","_")+".sources.xml","w+")
  204. sourcefile.write("<sources><source type=\"gen_xmltv\" channels=\"" + EPGIMPORTPATH +"suls_iptv_"+bouquet.replace(" ","_").replace("/","_")+".channels.xml\">\n")
  205. sourcefile.write("<description>IPTV - "+bouquet.replace("/"," ")+"</description>\n")
  206. sourcefile.write("<url>" + epgurl[0].replace("&","&amp;") + "</url>\n")
  207. sourcefile.write("</source></sources>\n")
  208. sourcefile.close()
  209.  
  210. # crontab not installed in enigma by default / pip also missing - not sure how to get this in at the moment
  211. #def create_cron():
  212. #if not TESTRUN:
  213. #print("\n----Creating cron job----")
  214. #cron = CronTab(user="root")
  215. #job = cron.new(command = 'python /home/root/e2m3u2bouquet.py "http://s...content-available-to-author-only...v.com:25461/get.php?username=UN&password=PW&type=m3u_plus&output=ts" "http://s...content-available-to-author-only...v.com:25461/xmltv.php?username=UN&password=PW"')
  216. #job.comment = "e2m3u2bouquet"
  217. #job.minute.every(2)
  218. #cron.write()
  219. #print("cron job created, bouquets will autoupdate...")
  220.  
  221. def main(argv=None): # IGNORE:C0111
  222. #Command line options.
  223. if argv is None:
  224. argv = sys.argv
  225. else:
  226. sys.argv.extend(argv)
  227. program_name = os.path.basename(sys.argv[0])
  228. program_version = "v%s" % __version__
  229. program_build_date = str(__updated__)
  230. program_version_message = '%%(prog)s %s (%s)' % (program_version, program_build_date)
  231. program_shortdesc = __import__('__main__').__doc__.split("\n")[1]
  232. program_license = '''%s
  233.  
  234. Copyright 2017. All rights reserved.
  235. Created on %s.
  236. Licensed under the Apache License 2.0
  237. http://w...content-available-to-author-only...e.org/licenses/LICENSE-2.0
  238.  
  239. Distributed on an "AS IS" basis without warranties
  240. or conditions of any kind, either express or implied.
  241.  
  242. USAGE
  243. ''' % (program_shortdesc, str(__date__))
  244.  
  245. try:
  246. # Setup argument parser
  247. parser = ArgumentParser(description=program_license, formatter_class=RawDescriptionHelpFormatter)
  248. parser.add_argument(dest="m3uurl", help="URL to download m3u data from ", metavar="m3uurl", nargs='+')
  249. parser.add_argument(dest="epgurl", help="URL source for XML TV epg data sources ", metavar="epgurl", nargs='+')
  250. parser.add_argument("-i", "--iptvtypes", dest="iptvtypes", action="store_true", help="Treat all stream references as IPTV stream type. (required for some enigma boxes)")
  251. parser.add_argument("-m", "--multivod", dest="multivod", action="store_true", help="Create multiple VOD bouquets based on category rather than 1 bouquet for all VOD content")
  252. parser.add_argument("-U", "--uninstall", dest="uninstall", action="store_true", help="Uninstall all changes made by this script")
  253. parser.add_argument('-V', '--version', action='version', version=program_version_message)
  254. # Process arguments
  255. args = parser.parse_args()
  256. m3uurl = args.m3uurl[0]
  257. epgurl = args.epgurl
  258. iptvtypes = args.iptvtypes
  259. uninstall = args.uninstall
  260. multivod = args.multivod
  261.  
  262. except KeyboardInterrupt:
  263. ### handle keyboard interrupt ###
  264. return 0
  265.  
  266. except Exception, e:
  267. if DEBUG or TESTRUN:
  268. raise(e)
  269. indent = len(program_name) * " "
  270. sys.stderr.write(program_name + ": " + repr(e) + "\n")
  271. sys.stderr.write(indent + " for help use --help")
  272. return 2
  273. # welcome message
  274. print("\n********************************")
  275. print("Starting Engima2 IPTV bouquets")
  276. print("********************************\n")
  277. # Clean up any existing files
  278. uninstaller()
  279. if uninstall:
  280. print("Uninstall only, program exiting ...")
  281. sys.exit(1) # Quit here if we just want to uninstall
  282. # Download m3u
  283. m3ufile = download_m3u(m3uurl)
  284. # Test m3u format
  285. #### TO DO ####
  286. # parse m3u file
  287. listchannels = parsem3u(m3ufile,iptvtypes,multivod)
  288. # Create bouquet files
  289. create_bouquets(listchannels)
  290. # Create list of bouquets
  291. bouquets= []
  292. for x in listchannels:
  293. if x[0] not in bouquets:
  294. bouquets.append(x[0])
  295. # Now create custom channels for each bouquet
  296. print("\n----Creating custom channels----")
  297. for bouquet in bouquets:
  298. create_custom_channel(bouquet,listchannels)
  299. print("custom channels created...")
  300. # Finally create custom sources for each bouquet
  301. print("\n----Creating custom sources----")
  302. for bouquet in bouquets:
  303. create_custom_source(bouquet,epgurl)
  304. print("custom sources created...")
  305. # Now create a cron job
  306. #create_cron()
  307. # *Borrow* dougs better ending message and reload bouquets code
  308. # reload bouquets
  309. reloadBouquets()
  310. print("\n********************************")
  311. print("Engima2 IPTV bouquets created ! ")
  312. print("********************************")
  313. print("\nTo enable EPG data")
  314. print("Please open EPGImport plugin.. ")
  315. print("Select sources and enable the new IPTV sources (these start IPTV - )")
  316. print("Save the selected sources, press yellow button to start manual import")
  317. print("You can then set EPGImport to automatically import the EPG every day")
  318.  
  319. if __name__ == "__main__":
  320. #if DEBUG:
  321. if TESTRUN:
  322. EPGIMPORTPATH="H:/Satelite Stuff/epgimport/"
  323. ENIGMAPATH="H:/Satelite Stuff/enigma2/"
  324. sys.exit(main())# your code goes here
Runtime error #stdin #stdout #stderr 0s 25144KB
stdin
Standard input is empty
stdout
Standard output is empty
stderr
Traceback (most recent call last):
  File "prog.py", line 324, in <module>
  File "prog.py", line 231, in main
AttributeError: 'NoneType' object has no attribute 'split'