Enigma2 plugin to dowload and create channels picons files form lyngsat.com

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. # coding: utf-8
  2. #
  3. # This file is part of GetPicons - enigma2 plugin to download picons from lyngsat.com
  4. # Copyright (c) 2016 ivars777 (ivars777@gmail.com)
  5. # Distributed under the GNU GPL v3. For full terms see http://www.gnu.org/licenses/gpl-3.0.en.html
  6. """
  7. Dowload and create Enigma2 channels' picons files form lyngsat.com
  8. Usage [options]
  9. Options:
  10. -p PACKAGE_LIST, --package=PACKAGE_LIST list of package names (html file name in lyngsat),
  11. e.g "viasat,ntvplus36"
  12. -s SAT_LIST, --sat=SAT_LIST list of sattelite positions, e.g. "4.9,-0.8"
  13. -z SIZE icon size, e.g. "100x60", default - "220x132"
  14. -f PATH, --folder PATH picon files output folder, default - "/media/hdd/picon"
  15. -e PATH, --enigma PATH enigma2 folder whera lamedb,settings are located, default - "/etc/enigma2"
  16. you can use urls, e.g. "ftp://root@receiver_address/etc/enigma2"
  17. -o, -overwrite overwrite existing picons file (default - no)
  18. -d, -debug display work progress and write debug info to file for not found services
  19. -h, --help this help file
  20. (c)Ivars777 (ivars777@gmail.com) 2013-2015, v0.2
  21. """
  22. import sys, os, os.path, getopt
  23. import re
  24. import urllib2
  25. import requests
  26. try:
  27. import Image
  28. except:
  29. from PIL import Image
  30. from StringIO import StringIO
  31. import logging
  32. options = args = None
  33. _sd= lambda x,d: d if x is None else x # set default value
  34. _sl= lambda x: False if x is None else True # set True of False for option
  35. def parse_arguments(argv,opt_short,opt_long):
  36. "Parse command line arguments"
  37. try:
  38. options, args = getopt.gnu_getopt(argv, opt_short,opt_long)
  39. options = Rec(dict([(o[0].replace("-",""),o[1]) for o in options]))
  40. except getopt.GetoptError, err:
  41. print err.msg
  42. print __doc__
  43. sys.exit(2)
  44. if options.has_key("h") or options.has_key("help"):
  45. print __doc__
  46. sys.exit(2)
  47. return options,args
  48. services = None
  49. def main(argv):
  50. global options, args, services
  51. # Parsing options
  52. opt_short = 'p:s:z:f:e:odh'
  53. opt_long = ["package=","sat","folder=","enigma=","overwrite","debug","help"]
  54. options,args = parse_arguments(argv[1:], opt_short, opt_long)
  55. options.package = _sd(options.package,_sd(options.p,""))
  56. options.sat = _sd(options.sat,_sd(options.s,""))
  57. options.enigma = _sd(options.enigma,_sd(options.e,"/etc/enigma2"))
  58. options.folder = _sd(options.folder,_sd(options.f,"/media/hdd/picon"))
  59. options.size = _sd(options.size,_sd(options.z,"220x132"))
  60. options.overwrite = _sl(_sd(options.overwrite,options.o))
  61. options.debug = _sl(_sd(options.debug,options.d))
  62. options.w,options.h = map(int,options.size.split("x"))
  63. if not os.path.exists(options.folder):
  64. os.makedirs(options.folder)
  65. if options.debug:
  66. #filename="get_picons.log" if not os.name == "posix" else "/var/log/get_picons.log"
  67. filename = os.path.join(options.folder,"get_picons.log")
  68. FORMAT = "%(asctime)-15s %(message)s"
  69. logging.basicConfig(filename=filename, format=FORMAT,level=logging.DEBUG)
  70. print "** Analyzing lamedb"
  71. services = DBServices(options.enigma)
  72. if options.sat:
  73. package_list = []
  74. for sat in options.sat.split(","):
  75. package_list.extend(get_packages(sat))
  76. elif options.package:
  77. package_list = options.package.split(",")
  78. else: # get sattelite list from Enigma settings
  79. sat_list = []
  80. package_list = []
  81. if not "ftp:" in options.enigma:
  82. fname = os.path.join(options.enigma,"settings")
  83. f = open(fname)
  84. else:
  85. sep = "" if options.enigma[-1]=="//" else "//"
  86. fname = options.enigma + sep + "settings"
  87. f = urllib2.urlopen(fname)
  88. for line in f:
  89. if not "config.Nims.0.advanced.sat." in line: continue
  90. sat_list.append(line.split(".")[5])
  91. for sat in sat_list:
  92. package_list.extend(get_packages(sat))
  93. for package in package_list:
  94. create_picons(package)
  95. def create_picons(package):
  96. "Create picons files for package"
  97. global services,options
  98. url_package = "http://www.lyngsat.com/packages/%s.html"%package
  99. if options.debug:
  100. print "** Downloading and looking %s for picons"%url_package
  101. num_picons = 0
  102. num_skipped = 0
  103. num = 0
  104. try:
  105. #soup = html = urllib2.urlopen(url_package).read().decode("latin1")
  106. soup = html = get_page(url_package).decode("latin1")
  107. except:
  108. raise Exception("Package page '%s' does not found"%url_package)
  109. title = re.search("<title>(.+?)</title>", soup, re.DOTALL | re.IGNORECASE).group(1)
  110. pos = re.search("\d+\.\d+",title).group().replace(".","")
  111. ew = re.search("\xb0.",title).group()[1]
  112. ns = int(pos)
  113. if ew == "W":
  114. ns = 3600 - ns
  115. if ns==48: ns=49
  116. if ns==359: ns=360
  117. #pos = "%04x0000"%ns
  118. #pos_num = int(pos[0:4],16)
  119. title2 = title[0:title.index("- LyngSat")]
  120. print "** %s: "%title2.replace(u'\xb0',""),
  121. tables = re.findall(r"<table width=720 border cellspacing=0 cellpadding=0>\s*(?:(?=<tr>))(.+?)</table>", soup, re.DOTALL | re.IGNORECASE)
  122. for table in tables:
  123. tr= html_findall("tr",table)
  124. for i in range(2,len(tr)-1):
  125. td= html_findall("td",tr[i])
  126. if len(td) == 10:
  127. freq,polar = re.search(r"<b>(\d+) ([HVLR])", tr[i], re.DOTALL | re.IGNORECASE).groups()
  128. freq = int(freq)
  129. p_value = {"H":0,"V":1,"L":2,"R":3}
  130. polar = p_value[polar]
  131. b=1
  132. else:
  133. b=0
  134. icon_url = html_attr("src",td[b])
  135. if icon_url:
  136. icon_url = "http://www.lyngsat.com" + icon_url.group(1).replace("/icon","/logo").replace(".gif",".jpg")
  137. else:
  138. icon_url = ""
  139. name = html_text(td[b+1]).group(1)
  140. sid = html_text(td[b+5])
  141. sid = sid.group(1).replace("&nbsp;","") if sid else ""
  142. sid = int(sid) if sid and not sid == u"-" else ""
  143. vpid = html_text(td[b+6])
  144. vpid = vpid.group(1).replace("&nbsp;","") if vpid else ""
  145. #try:
  146. # vpid = int(vpid)
  147. #except Exception:
  148. # vpid = None
  149. #vpid = int(vpid) if vpid and not vpid == u"-" else None
  150. if not sid or not vpid or icon_url=="":
  151. continue
  152. if options.debug:
  153. print " package:%i/%s: service:%s "%(ns,package, name.encode("ascii","replace")),
  154. num += 1
  155. sref = find_sref(ns,freq,polar,sid)
  156. if not sref:
  157. sref = find_sref(ns,freq+1,polar,sid)
  158. if not sref:
  159. sref = find_sref(ns,freq-1,polar,sid)
  160. if not sref:
  161. if options.debug:
  162. print " -- NOK (no sref in lamedb!)"
  163. logging.debug(u" no sref - package:%i/%s: service:%s pos:%s/%s freq:%i polar:%s sid:%i/%x"%(ns,package, name,ns, pos_int2str(ns),freq,polar,sid,sid))
  164. continue
  165. fname = sref.replace(":","_")
  166. fname = fname[:-1]+".png"
  167. fname = os.path.join(options.folder, fname)
  168. if os.path.exists(fname) and not options.overwrite:
  169. if options.debug: print " -- skipped"
  170. num_skipped += 1
  171. continue
  172. #try:
  173. #im = Image.open(StringIO(urllib2.urlopen(icon_url).read()))
  174. data = get_page(icon_url)
  175. im = Image.open(StringIO(data))
  176. #im = im.crop((0,10,132,89))
  177. im = im.resize((options.w,options.h), Image.ANTIALIAS)
  178. im = im.convert('P', palette=Image.ADAPTIVE)
  179. #except Exception:
  180. # im = None
  181. if not im:
  182. if options.debug: print " -- NOK (no picon image)"
  183. continue
  184. if options.debug: print " -- downloaded"
  185. im.save(fname,"png")
  186. num_picons += 1
  187. print "%i picons created, %i skipped"%(num_picons,num_skipped)
  188. def get_packages(sat):
  189. "Get packages list (names) for sattelite from lyngsat.com page"
  190. global options
  191. sat= sat.replace(".","")
  192. num = int(sat)
  193. if num>1800: num = num - 3600
  194. if num>0 and num<730: soup = get_soup("europe")
  195. elif num>730 and num<1600: soup = get_soup("asia")
  196. elif num<0 and num>-610: soup = get_soup("atlantic")
  197. elif num<-610 and num>-1600: soup = get_soup("america")
  198. else:
  199. return []
  200. i1,i2 = gr=re.search(r"<table cellspacing=0 border>\s*(?:(?=<tr>))(.+?)</table>", soup, re.DOTALL | re.IGNORECASE).span()
  201. packages = []
  202. for s in re.findall("<tr>(.+?)</tr>", soup[i1:i2], re.DOTALL | re.IGNORECASE):
  203. gr = re.search(r"(\d+.)</font><font size=1>(\d).+#176;([EW])", s,re.DOTALL | re.IGNORECASE).groups()
  204. pos = int("".join(gr[:-1]).replace(".",""))
  205. if gr[2] == "W": pos = -pos
  206. if abs(num - pos) <=2:
  207. package = re.search(r"www\.lyngsat\.com/packages/(\w.+?)\.html", s, re.DOTALL | re.IGNORECASE).group(1)
  208. packages.append(package)
  209. return packages
  210. soups = {}
  211. def get_soup(region):
  212. if soups.has_key(region):
  213. return soups[region]
  214. url = "http://www.lyngsat.com/packages/%s.html"%region
  215. if options.debug:
  216. print "** Downloading and looking %s for packages"%url
  217. try:
  218. #html = urllib2.urlopen(url).read().decode("latin1")
  219. html = get_page(url).decode("latin1")
  220. except:
  221. raise Exception("Packege page '%s' does not found"%url)
  222. soups[region] = html
  223. return soups[region]
  224. def find_sref(ns,freq,polar,sid):
  225. "Find service reference according to given parameters"
  226. global services
  227. tp = services.get_transp_by_pic((ns,freq,polar))
  228. if tp:
  229. serv = services.get_service_by_chid((sid,tp["tsid"],tp["onid"]))
  230. if serv:
  231. return serv["sref"]
  232. class DBServices(object):
  233. """Dreambox services, stored in lamedb (E2)/services(E1) file handling class"""
  234. def __init__(self,service_path,enigma=2):
  235. self.enigma = enigma
  236. self.service_path = service_path
  237. if self.enigma == 2:
  238. if not "ftp:" in service_path:
  239. self.service_fname= os.path.join(service_path,"lamedb")
  240. self.bouquets_fname = os.path.join(service_path, "bouquets.tv")
  241. else:
  242. sep = "" if service_path[-1]=="//" else "//"
  243. self.service_fname= service_path+sep+"lamedb"
  244. self.bouquets_fname = service_path+sep+"bouquets.tv"
  245. else: # ENIGMA === "1":
  246. self.service_fname = os.path.join(service_path,"services")
  247. self.bouquets_fname = os.path.join(service_path, "userbouquets.tv.epl")
  248. self.services = []
  249. self.index_sref= {}
  250. self.index_chid = {}
  251. self.index_name = {}
  252. self.index_provider = {}
  253. self.bouquets = []
  254. self.bouquets_services = {}
  255. self.transp = []
  256. self.tp_index_tpid = {}
  257. self.tp_index_pic ={}
  258. self._load_services()
  259. #self._load_bouquets()
  260. def _load_services(self):
  261. #tpref - lamedb/transponders
  262. # NS:TSID:ONID
  263. # s FREQ:SR:POLAR:FREC:49:2:0
  264. # X X X
  265. # D D D D
  266. # lref - lamedb/services
  267. # SID:NS:TSID:ONID:STYPE:UNUSED(channelnumber in enigma1)
  268. # 0 1 2 3 4 5
  269. # X X X X D D
  270. # wref bouquets/picon
  271. # REFTYPE:FLAGS:STYPE:SID:TSID:ONID:NS:PARENT_SID:PARENT_TSID:UNUSED
  272. # 0 1 2 3 4 5 6 7 8 9
  273. # D D X X X X X X X X
  274. f_services = open(self.service_fname,"r") if not self.service_fname[0:4] == "ftp:" else urllib2.urlopen(self.service_fname)
  275. line = f_services.readline()
  276. if not "eDVB services" in line:
  277. raise Exception("no correct lamedb file")
  278. line = f_services.readline()
  279. # Read transponders
  280. i = 0
  281. while True:
  282. line = f_services.readline()
  283. if "end" in line: break
  284. #tp = record.Rec()
  285. tp = {}
  286. ff = line.strip().split(":")
  287. tp["ns"] = ns = pos_str2int(ff[0])
  288. tp["tsid"] = tsid = int(ff[1],16)
  289. tp["onid"] = onid = int(ff[2],16)
  290. line = f_services.readline()
  291. tp["params"] = params = line.strip().split(" ")[1]
  292. ff = params.split(":")
  293. tp["freq"] = freq = int(ff[0][:-3])
  294. tp["polar"] = polar = int(ff[2])
  295. tp["tpid"] = tpid = (ns,tsid,onid)
  296. self.transp.append(tp)
  297. self.tp_index_tpid[tpid] = i
  298. self.tp_index_pic[(ns,freq,polar)] = i
  299. line = f_services.readline()
  300. i += 1
  301. # Reading services
  302. i= 0
  303. line = f_services.readline()
  304. while True:
  305. line = f_services.readline()
  306. if line[0:3] == "end": break
  307. if not line: break
  308. #rec = record.Rec()
  309. rec = {}
  310. rec["lref"] = line.lower().strip().decode("utf-8")
  311. line = f_services.readline()
  312. #line = line.replace('\xc2\x87', '').replace('\xc2\x86', '')
  313. #line = decode_charset(line)
  314. line = line.decode("utf-8")
  315. rec["name"] = line.strip()
  316. line = f_services.readline()
  317. pl = line.split(",")
  318. provider = ""
  319. for p in pl:
  320. if p[0:2] == "p:":
  321. provider = p[2:].strip()
  322. break
  323. if provider == "":
  324. provider = "unknown"
  325. rec["provider"] = provider.decode("utf-8")
  326. rec["sref"] = lref2sref(rec["lref"])
  327. r = lref_parse(rec["lref"])
  328. rec["stype"] = r["stype"]
  329. rec["chid"] = (r["sid"],r["tsid"],r["onid"])
  330. self.services.append(rec)
  331. self.index_sref[rec["sref"]] = i
  332. self.index_chid[rec["chid"]] = i
  333. name = rec["name"].lower()
  334. if not self.index_name.has_key(name): self.index_name[name] = []
  335. self.index_name[name].append(i)
  336. provider = rec["provider"].lower()
  337. if not self.index_provider.has_key(provider): self.index_provider[provider] = []
  338. self.index_provider[provider].append(i)
  339. i += 1
  340. f_services.close()
  341. def _load_bouquets(self):
  342. f_bouquets = open(self.bouquets_fname,"r") if not self.bouquets_fname[0:4] == "ftp:" else urllib2.urlopen(self.bouquets_fname)
  343. for line in f_bouquets.readlines():
  344. if line[0:9] == "#SERVICE:":
  345. if self.enigma==1:
  346. bn = line.strip().split("/")
  347. else:
  348. bn = line.strip().split(":")
  349. bfn = bn[-1]
  350. bf = open(os.path.join(self.service_path,bfn))
  351. for line2 in bf.readlines():
  352. if line2[0:5] == "#NAME":
  353. bname = line2.strip()[6:]
  354. self.bouquets.append(bname)
  355. self.bouquets_services[bname] = []
  356. elif line2[0:8] == "#SERVICE":
  357. sref = line2.strip()[9:]
  358. r = sref_parse(sref)
  359. if r["flags"] == 64: continue
  360. self.bouquets_services[bname].append(sref)
  361. bf.close()
  362. f_bouquets.close()
  363. def get_bouquets(self):
  364. if not self.bouquets:
  365. self._load_bouquets()
  366. return self.bouquets
  367. def get_bouquet_services(self,bn):
  368. if not self.bouquets:
  369. self._load_bouquets()
  370. return [self.get_service_by_sref(sref) for sref in self.bouquets_services[bn]]
  371. def get_service_by_sref(self,sref):
  372. return self.services[self.index_sref[sref]] if self.index_sref.has_key(sref) else None
  373. def get_service_by_chid(self,chid):
  374. return self.services[self.index_chid[chid]] if self.index_chid.has_key(chid) else None
  375. def get_service_by_name(self,name):
  376. return [self.services[i] for i in self.index_name[name]]
  377. def get_service_by_provider(self,provider):
  378. return [self.services[i] for i in self.index_provider[provider]]
  379. def get_transp_by_tpid(self,tpid):
  380. return self.transp[self.tp_index_tpid[tpid]] if self.tp_index_tpid.has_key(tpid) else None
  381. def get_transp_by_pic(self,picid):
  382. return self.transp[self.tp_index_pic[picid]] if self.tp_index_pic.has_key(picid) else None
  383. def decode_charset(s):
  384. u = None
  385. if isinstance(s,unicode):
  386. return s
  387. charset_list=('iso-8859-4','iso-8859-1','iso-8859-2''iso-8859-15')
  388. for charset in charset_list:
  389. try:
  390. u=unicode(s,charset,"strict")
  391. except:
  392. pass
  393. else:
  394. break
  395. if u == None:
  396. raise Error, "CHARSET ERROR while decoding lamedb. Aborting !"
  397. else:
  398. return(u)
  399. # lref - lamedb/services
  400. # SID:NS:TSID:ONID:STYPE:UNUSED(channelnumber in enigma1)
  401. # 0 1 2 3 4 5
  402. # X X X X D D
  403. #
  404. # wref bouquets/picon
  405. # REFTYPE:FLAGS:STYPE:SID:TSID:ONID:NS:PARENT_SID:PARENT_TSID:UNUSED
  406. # 0 1 2 3 4 5 6 7 8 9
  407. # D D X X X X X X X X
  408. def lref2sref(s):
  409. "Converts service refefence from lamedb format to bouquets/picon (standard) format"
  410. f = s.split(":")
  411. sid = int(f[0],16)
  412. ns = int(f[1], 16)
  413. tsid = int(f[2],16)
  414. onid = int(f[3],16)
  415. stype = int(f[4])
  416. s2 = "1:0:%X:%X:%X:%X:%X:0:0:0:" % (stype,sid,tsid,onid,ns)
  417. return s2
  418. def lref_parse(s):
  419. "Parse lamedb/service string, return dictionary of items"
  420. f = s.split(":")
  421. r = {}
  422. r["sid"] = int(f[0],16)
  423. r["ns"] = int(f[1], 16)
  424. r["tsid"] = int(f[2],16)
  425. r["onid"] = int(f[3],16)
  426. r["stype"] = int(f[4])
  427. return r
  428. def sref2lref(s):
  429. "Converts service refefence from bouquets/picon (standard) format to lamedb format"
  430. f = s.split(":")
  431. reftype = int(f[0])
  432. flags = int(f[1])
  433. stype = int(f[2],16)
  434. sid = int(f[3],16)
  435. tsid = int(f[4],16)
  436. onid = int(f[5],16)
  437. ns = int(f[6],16)
  438. s2 = "%04x:%08x:%04x:%04x:%i:0" % (sid,ns,tsid,onid,stype)
  439. return s2
  440. def sref_parse(s):
  441. "Parse service refefence string , return dictionary of items"
  442. f = s.split(":")
  443. r = {}
  444. r["reftype"] = int(f[0])
  445. r["flags"] = int(f[1])
  446. r["stype"] = int(f[2],16)
  447. r["sid"] = int(f[3],16)
  448. r["tsid"] = int(f[4],16)
  449. r["onid"] = int(f[5],16)
  450. r["ns"] = int(f[6],16)
  451. return r
  452. pos_int2str = lambda pos_num: "%04x0000"%pos_num
  453. pos_str2int = lambda pos: int(pos[0:4],16)
  454. def decode_charset(s):
  455. u = None
  456. charset_list = ('utf-8','iso-8859-1','iso-8859-2','iso-8859-15')
  457. for charset in charset_list:
  458. try:
  459. u = unicode(s,charset,"strict")
  460. except:
  461. pass
  462. else:
  463. break
  464. if u == None:
  465. print("CHARSET ERROR while decoding string")
  466. sys.exit(1)
  467. else:
  468. return(u)
  469. headers2dict = lambda h: dict([l.strip().split(": ") for l in h.strip().splitlines()])
  470. headers = headers2dict("""
  471. User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0
  472. Referer: http://www.lyngsat.com/
  473. """)
  474. def get_page(url):
  475. r=requests.get(url,headers=headers)
  476. return r.content
  477. html_text = lambda html: re.search(r">([^<^\n]+?)<", html, re.DOTALL | re.IGNORECASE)
  478. html_tag = lambda tag, html: re.search(r"<%s\b[^>]*>(.*?)</%s>"%(tag,tag), html, re.DOTALL | re.IGNORECASE)
  479. html_attr = lambda attr,html: re.search("""%s="?([^'" >]+)"""%(attr), html, re.DOTALL | re.IGNORECASE)
  480. html_findall_content = lambda tag,html: re.findall(r"<%s\b[^>]*>(.*?)</%s>"%(tag,tag), html, re.DOTALL | re.IGNORECASE)
  481. html_findall = lambda tag,html: re.findall(r"<%s\b[^>]*>.*?</%s>"%(tag,tag), html, re.DOTALL | re.IGNORECASE)
  482. import UserDict
  483. class Rec(UserDict.DictMixin):
  484. "Dict like class allowing a.x instead of a['x']"
  485. def __init__(self, dict=None, **kwargs):
  486. self.__dict__ = {}
  487. if dict is not None:
  488. self.update(dict)
  489. if len(kwargs):
  490. self.update(kwargs)
  491. def __setitem__(self, name, value): setattr(self, name, value)
  492. def __getitem__(self, name): return getattr(self,name,None)
  493. def __getattr__(self,key): return None
  494. def has_key(self,key): return True if self.__dict__.has_key(key) else False
  495. def keys(self): return list(self.__dict__)
  496. if __name__ == "__main__":
  497. #print "** Starting..."
  498. sys.exit(main(sys.argv))