#!/usr/bin/env python # coding=utf8 # # # This file is part of PlayStream - enigma2 plugin to play video streams from various sources # Copyright (c) 2016 ivars777 (ivars777@gmail.com) # Distributed under the GNU GPL v3. For full terms see http://www.gnu.org/licenses/gpl-3.0.en.html # Used fragments of code from enigma2-plugin-tv3play by Taapat (https://github.com/Taapat/enigma2-plugin-tv3play) # import os,time,sys,os,os.path import datetime,re import urllib2 from enigma import ePicLoad, eServiceReference, eTimer, getDesktop from Components.ActionMap import ActionMap,HelpableActionMap from Screens.InfoBarGenerics import InfoBarShowHide from Components.AVSwitch import AVSwitch from Components.Label import Label from Components.MenuList import MenuList from Components.Pixmap import Pixmap from Components.Sources.List import List from Components.Sources.StaticText import StaticText from Components.Button import Button from Plugins.Plugin import PluginDescriptor from Screens.InfoBar import MoviePlayer from Screens.MessageBox import MessageBox from Tools import Notifications from Screens.LocationBox import LocationBox #from Screens.InputBox import InputBox from Screens.ChoiceBox import ChoiceBox from Screens.VirtualKeyBoard import VirtualKeyBoard from Components.Input import Input from Screens.Screen import Screen from Tools.BoundFunction import boundFunction from Tools.Directories import resolveFilename, SCOPE_PLUGINS from Tools.LoadPixmap import LoadPixmap from twisted.web.client import downloadPage,defer,reactor from Components.Task import job_manager from Components.config import config, ConfigSubsection, ConfigText, ConfigInteger, ConfigLocations, ConfigDirectory, ConfigSet, ConfigYesNo, ConfigSelection, getConfigListEntry, ConfigSelectionNumber import ContentSources import util from sources.SourceBase import stream0 from VideoDownload import downloadJob, HLSDownloadJob,VideoDownloadList #import enigma2_api TMPDIR = "/tmp/playstream/" e2 = None config.playstream = ConfigSubsection() config.playstream.locations = ConfigLocations(default=["/media/hdd/movie/"]) config.playstream.download_dir = ConfigText(default="/media/hdd/movie/") def make_service(stream): url = stream["url"] headers = [] if stream.has_key("headers"): for h in stream["headers"]: headers.append("%s=%s"%(h,stream["headers"][h])) if headers: headers ="&".join(headers) url = url+"#"+headers print "stream_url with headers=",headers url = url.encode("utf8") if "|" in url: url = url.replace("|","#") service_type = 4097 if re.search("\.ts$",url.split("?")[0],re.IGNORECASE): service_type = 1 if "resolver" in stream and stream["resolver"] in ("hqq","viaplay"): url = util.streamproxy_encode(stream["url"], stream["headers"]) if os.path.exists("/usr/bin/exteplayer3"): # problem playing hqq streams with gstreamer, use exteplayer3 service_type = 5002 print "service_type=",service_type print "stream_url=",url service = eServiceReference(service_type, 0, url) service.setName( stream["name"]) return service ##################################################################################################################### class PSSubs(Screen): def __init__(self, session): desktopWidth = getDesktop(0).size().width() desktopHeight = getDesktop(0).size().height() offset = 20 screenWidth = desktopWidth - (2 * offset) widgetWidth = screenWidth / 2 - 5 self.skin = """ """ % (offset, desktopHeight-offset-140, screenWidth, screenWidth) self['subtitle'] = Label() Screen.__init__(self, session) ##################################################################################################################### class PSPlayer(MoviePlayer): def __init__(self, session, streams): print "PSPlayer init: ",streams self.session = session self.streams = streams self.cur_stream = self.streams[0] # TODO self.selected = 0 self.resume_pos = 0 service = make_service(self.cur_stream) MoviePlayer.__init__(self, session, service) self.skinName = "MoviePlayer" self["actions"] = ActionMap(["MediaPlayerActions","MediaPlayerSeekActions","MoviePlayerActions","InfobarSeekActions","MovieSelectionActions","ColorActions"], { "stop":self.leavePlayer, "leavePlayer":self.leavePlayer, "audio":self.select_stream, "AudioSelection":self.select_stream, "green":self.select_stream, "subtitles":self.select_captions, "text":self.select_captions, "yellow_key":self.select_captions, "yellow":self.select_captions, "pauseServiceYellow":self.select_captions, "pause":self.select_captions, "showEventInfo":self.service_info, "info":self.service_info }) self.stimer = eTimer() if "callback" in dir(self.stimer): self.stimer.callback.append(self.update_subtitles) elif "timeout": self.stimer_conn = self.stimer.timeout.connect(self.update_subtitles) else: self.stimer = None self.stimer_step = 500 if self.cur_stream["subs"]: self.subs = self.cur_stream["subs"] self.cur_subs = 0 # TODO - no konfiguracijas self.svisible = True # TODO - no konfiguracija self.sind = 0 self.get_subs_current() else: self.subs = [] self.cur_subs = 0 self.svisible = False self.subtitle_window = self.session.instantiateDialog (PSSubs) self.onLayoutFinish.append(self.start_subtitles_timer) def start_subtitles_timer(self): if self.stimer: self.subtitle_window.show() print "start_subtitles_timer" self.stimer.start(self.stimer_step) def get_sub_pts(self,pts): sc = self.get_sub_ind(self.sind) # current subbtitle while True: if not sc: return "Error - no subs find" # TODO if pts > sc["end"]: self.sind += 1 sc = self.get_sub_ind(self.sind) continue else: if pts { "template": [MultiContentEntryText(pos=(10, 1), size=(560, 30), \ font=0, flags=RT_HALIGN_LEFT, text=0)], "fonts": [gFont("Regular", 20)], "itemHeight": 30 } """ def __init__(self, session): Screen.__init__(self, session) #self.setTitle2("Home") self.session = session self.e2 = None self["key_red"] = Button(_("Exit")) self["key_green"] = Button(_("Select")) self["key_yellow"] = Button(_("Options")) self["key_menu"] = Button("Menu") self["key_exit"] = Button("Back") self["actions"] = ActionMap(["OkCancelActions", "ColorActions","MenuActions", "NumberActions"], { #"cancel": self.Cancel, "ok": self.Ok, "green": self.Ok, "red": self.Cancel, "yellow": self.options_screen, "blue": self.download_list, "menu": self.item_menu, "cancel": self.Back, }) self["list"] = List([]) self["list"].onSelectionChanged.append(self.SelectionChanged) self["pic"] = Pixmap() self["cur"] = Label() self["title"] = Label() self.downloading = 0 self.activeDownloads = 0 if not os.path.exists(TMPDIR): os.mkdir(TMPDIR) self.onLayoutFinish.append(self.LayoutFinish) def LayoutFinish(self): self.cur_directory = os.path.dirname(os.path.realpath(__file__)) self.defimage = LoadPixmap(os.path.join(self.cur_directory,"PlayStream.png")) #self.defimage = None #sc = AVSwitch().getFramebufferScale() #self.defimage0 = ePicLoad() #self.defimage0.PictureData.get().append(boundFunction(self.FinishDecodeDef, os.path.join(self.cur_directory,"PlayStream.png"))) #self.defimage0.setPara((self["pic"].instance.size().width(),self["pic"].instance.size().height(),sc[0], sc[1], False, 0, "#00000000")) #self.defimage0.startDecode("default") self.activeDownloads = 0 self.images = {} self.images_url = {} self.picloads = {} self.history = [] reload(ContentSources) self.sources = ContentSources.ContentSources(os.path.join(self.cur_directory,"sources")) self.config = self.sources.plugins["config"] self.cur_menu = ("Home","config::home","","Sākums") # self.content = self.sources.get_content(self.cur_menu[1]) #print self.content self["list"].setList(self.content) self["cur"].setText(self.content[0][3]) self.setTitle2(self.cur_menu[0]) self.ShowPic(self.content[0][2]) def SelectionChanged(self): current = self["list"].getCurrent() print "[PlayStream] SelectionChanged: current=",current if not current: return self["cur"].setText(current[3]) if current[3] else self["cur"].setText("") if current[2]: self.ShowPic(current[2]) else: self.ShowDefPic() def setTitle2(self,title): #print self.keys() self["title"].setText(title) def ShowDefPic(self): if self.defimage: self["pic"].instance.setPixmap(self.defimage) def ShowPic(self,image_url): if image_url == "default": self.ShowDefPic() return elif self.images.has_key(image_url): if self.images[image_url] in (-1,-2): return elif self.images[image_url] == -3: self.ShowDefPic() return else: self["pic"].instance.setPixmap(self.images[image_url]) else: if image_url.startswith("http"): fname = image_url.replace(":","-").replace("/","_") image_path = os.path.join(TMPDIR, fname) self.download_image(image_path, image_url) else: # local file image_path = os.path.join(self.cur_directory,image_url) self.start_decode(image_path,image_url) def start_decode(self,image_path,image_url): self.images[image_url] = -2 self.images_url[image_path] = image_url sc = AVSwitch().getFramebufferScale() if not self.picloads.has_key(image_path): self.picloads[image_path] = ePicLoad() self.picloads[image_path].PictureData.get().append(boundFunction(self.FinishDecode, image_path)) self.picloads[image_path].setPara((self["pic"].instance.size().width(), self["pic"].instance.size().height(), sc[0], sc[1], True, 0, "#00000000")) #print image_path,image_url self.picloads[image_path].startDecode(image_path) def FinishDecode(self, image_path,picInfo = None): image_url = self.images_url[image_path] del self.images_url[image_path] #III self.images[image_url] = self.picloads[image_path].getData() self["pic"].instance.setPixmap(self.images[image_url]) del self.picloads[image_path] if len(self.images)>30: del self.images[self.images.keys()[0]] # self.images.pop() #def FinishDecodeDef(self, image_path,picInfo = None): # self.defimage = self.defimage0.getData() # del self.defimage0 # self["pic"].instance.setPixmap(self.defimage) def download_image(self,image_path,image_url): #print "Image download started",self.downloading,image_path,image_url self.downloading += 1 self.images[image_url] = -1 downloadPage(image_url, image_path).addCallback(boundFunction(self.downloadFinished, image_path,image_url)).addErrback(boundFunction(self.downloadFailed, image_path,image_url)) def downloadFinished(self, image_path, image_url, result): self.downloading -= 1 #print "[ Play] Image downloaded finished ",self.downloading,image_path, image_url,result self.start_decode(image_path,image_url) def downloadFailed(self, image_path, image_url,result): self.downloading -= 1 print "[TV Play] Image downloaded failed ",self.downloading,image_path, image_url,result self.images[image_url] = -3 def Ok(self): current = self["list"].getCurrent() self.current = current index = self["list"].getIndex() self.index = index print "[PlayStream] - menu selected ", current data = current[1].split("::")[1] if "::" in current[1] else current[1] if not data: return elif self.sources.is_video(current[1]): if self.sources.stream_type(current[1]): stream = stream0.copy() stream["url"] = current[1] stream["name"] = current[0] streams = [stream] else: try: streams = self.sources.get_streams(current[1]) except Exception,e: print str(e) self.session. open(MessageBox, "Error - %s"%str(e) , MessageBox.TYPE_INFO) return if streams: #print streams for s in streams: if not s["name"]: s["name"] = current[0] if not s["img"]: s["img"] = current[2] if not s["desc"]: s["desc"] = current[3] try: self.session.open(PSPlayer, streams) except Exception as e: self.msg2("Error launching player - " + str(e)) return else: self.msg("No stream found - %s"%(self.current[1])) return elif current[1] == "back": cur_menu_old = self.cur_menu self.cur_menu = self.history.pop() new_content = self.sources.get_content(self.cur_menu[1]) try: index = zip(*new_content)[1].index(cur_menu_old[1]) except: index = 0 self.setTitle2(self.cur_menu[0]) self.show_content(new_content,index) else: print "selected=",current if "{0}" in current[1]: self.session.openWithCallback(self.cb_input,VirtualKeyBoard, title="Enter value", text="") #a = raw_input("Enter value:") #a = "big bang" #current = (current[0],current[1].format(a),current[2],current[3]) #self.get_content(current) else: self.get_content(current) def cb_input(self,value): if not value: return current = self.current current = (current[0],current[1].format(value),current[2],current[3]) self.get_content(current) def get_content(self,current): self.history.append(self.cur_menu) self.cur_menu = current try: new_content = self.sources.get_content(self.cur_menu[1]) except Exception,e: self.cur_menu = self.history.pop() self.session. open(MessageBox, "Error - %s"%str(e) , MessageBox.TYPE_INFO) return self.setTitle2(self.cur_menu[0]) self.show_content(new_content) def Back(self): self["list"].setIndex(0) self.Ok() def show_content(self,content,index=0): self["list"].setList(content) self["list"].setIndex(index) self.SelectionChanged() def Cancel(self): #if os.path.exists(TMPDIR): #for name in os.listdir(TMPDIR): #os.remove(os.path.join(TMPDIR, name)) #os.rmdir(TMPDIR) self.close() def item_menu(self): print "\n[PlayStream] options\n" self.current = self["list"].getCurrent() self.index = self["list"].getIndex() #self.session. open(MessageBox, "Item options - %s"%current[0] , MessageBox.TYPE_INFO) #args=[current,self.cur_menu] #self.session.open(ItemMenuScreen,current,index,self) lst = [ ("Aditional information","info","Display additional information about item"), ("Add to bouquet","bouquet","Add current item to Enigma2 bouquet"), ("Add to favorites","favorites","Add current item to PlayStrem favorites"), ("Show active downloads","download_list","Show active downloads list"), ("Set download folder","download_folder","Set download folder") ] if self.sources.is_video(self.current[1]): lst.extend([ ("Download video","download","Download video in background"), ]) else: lst.extend([ ("Download videos in folder","download","Download videos in folder (if any)"), ]) if "config::" in self.cur_menu[1]: lst.extend([ ("Rename item","rename","Rename list item"), ("Move item","move","Move list item"), ("Delete item","delete","Delete list item"), ("Add submenu","add_list","Add submenu before selected item"), ]) title = self.current[0] self.session.openWithCallback(self.cb_item_menu, ChoiceBox, title = title, list = lst) #TODO def cb_item_menu(self,answer): #print "item_menu_selected",answer if not answer: return if answer[1] == "info": self.session.open(MessageBox, "Not yet implemented!", MessageBox.TYPE_INFO) pass # TODO parada papildus info elif answer[1] == "bouquet": #if not e2: #e2 = enigma2_api.DBServices() #print "load_buuquets - ",e2._load_bouquets() self.session.open(MessageBox, "Not yet implemented!", MessageBox.TYPE_INFO) elif answer[1] == "favorites": lists = self.config.get_lists() lists2 = [(l,l) for l in lists] self.session.openWithCallback(self.cb_favorites, ChoiceBox, title="Selected menu item will be added",list = lists2) elif answer[1] == 'download': current = self.current if not self.sources.is_video(current[1]): #self.msg("Can not download listst (yet) - %s"%(current[1])) n = 0 for current2 in self.sources.get_content(current[1]): if self.sources.is_video(current2[1]): n += 1 self.download_video(current2) if n>0: self.msg("%s videos download started"%n) else: self.msg("No videos to download") else: if self.download_video(current): self.msg("Video download started") elif answer[1] == 'download_list': self.download_list() elif answer[1] == 'download_folder': #downloadDir = "/media/hdd/movie" #config.plugins.playstream.downloadDir.value TODO self.session.openWithCallback(self.select_download_dir, LocationBox,"Select download folder","",config.playstream.download_dir.value,config.playstream.locations,False,"Select folder",None,True,True) elif answer[1] == "delete": lst = self.cur_menu[1].replace("config::","") #print lst self.config.del_item(lst,self.index) self.config.write_streams() txt = "'%s' deleted from favourite stream list '%s'"%(self.current[0],lst) self.session.open(MessageBox, txt, MessageBox.TYPE_INFO,timeout=5) elif answer[1] == "rename": #name2 = "Renamed" self.session.openWithCallback(self.cb_rename,VirtualKeyBoard, title="Enter new item name", text=self.current[0]) elif answer[1] == "add_list": self.session.open(MessageBox, "Not yet implemented!", MessageBox.TYPE_INFO,timeout=5) pass #TODO return def select_download_dir(self, downloadDir, select=None): if not downloadDir: return print "Folder selected - %s"%downloadDir config.playstream.download_dir.setValue(downloadDir) config.playstream.download_dir.save() config.playstream.locations.save() config.save() def download_list(self): self.session.open(VideoDownloadList) def download_video(self,current): if self.sources.stream_type(current[1]): stream = stream0.copy() stream["url"] = current[1] stream["name"] = current[0] streams = [stream] else: try: streams = self.sources.get_streams(current[1]) except Exception,e: print "Error - %s"%str(e) self.session. open(MessageBox, "Error - %s"%str(e) , MessageBox.TYPE_INFO) return if not streams: self.msg("No stream found to download - %s"%(self.current[1])) return for s in streams: if not s["name"]: s["name"] = current[0] if not s["img"]: s["img"] = current[2] if not s["desc"]: s["desc"] = current[3] if len(streams)>1: stream = streams[0] # TODO iespeja izvelēties strīmu, ja to ir vairāki else: stream = streams[0] stream = util.stream_change(stream) return self.download_stream(stream) def download_stream(self,stream): print "download stream",stream #self.msg("Start downloading..") self.stream = stream stream_type = self.stream["type"] #self.sources.stream_type(stream["url"]) if not stream_type: # print "Not supported stream type found to download - %s"%(self.current[1]) self.msg("Not supported stream type found to download - %s"%(self.current[1])) return title = self.stream["name"].strip() url = self.stream["url"] stream_type = self.stream["type"] #self.sources.stream_type(stream["url"]) downloadDir = config.playstream.download_dir.value if not os.path.exists(downloadDir): print 'Sorry, download directory "%s" not exist!\nPlease specify in the settings existing directory'%downloadDir self.msg0(_('Sorry, download directory "%s" not exist!\nPlease specify in the settings existing directory'%downloadDir)) return fname0 = re.sub("[/\n\r\t,:]"," ",title) fname0 = re.sub("['""]","",fname0) fname = fname0 +".mp4" outputfile = os.path.join(downloadDir, fname) #print "Trying to download - ", current if self.stream["subs"]: suburl = self.stream["subs"][0]["url"] print "\n**Download subtitles %s - %s"%(title,suburl) subs = urllib2.urlopen(suburl).read() if subs: #fname0 = re.sub("[/\n\r\t,:]"," ",title) subext = ".srt" subfile = os.path.join(downloadDir,fname0+subext) if ".xml" in suburl: subs = util.ttaf2srt(subs) with open(subfile,"w") as f: f.write(subs) else: print "\n Error downloading subtitle %s"%suburl if os.path.exists(outputfile): self.msg( _('Sorry, this file already exists:\n%s') % outputfile) return False #os.remove(outputfile) if stream_type in ("http","https"): print "\n**Download %s - %s"%(title,url) #reload(downloadJob) job_manager.AddJob(downloadJob(url, outputfile, title[:20], self.video_download_stop)) self.activeDownloads += 1 #self.msg(_('Video download started!')) return True elif stream_type == "hls": #self.msg("HLS stream download not yet implemented!") print "\n**Download %s - %s"%(title,url) #reload(HLSDownloadJob) print "HLSDownload", url,outputfile job_manager.AddJob(HLSDownloadJob(url, outputfile, title[:20], self.video_download_stop)) self.activeDownloads += 1 #self.msg(_('Video download started!')) return True elif stream_type == "rstp": self.msg("RSTP stream download not yet implemented!") return False else: self.msg("Unkown stream type!") return False def cb_rename(self,value): if not value: return lst = self.cur_menu[1].replace("config::","") pos = self.index print value item2 = list(self.current) item2[0]=value item2 = tuple(item2) self.config.replace_item(lst,item2,pos) self.config.write_streams() txt = "'%s' renamed to '%s'"%(self.current[0],value) self.session.open(MessageBox, txt, MessageBox.TYPE_INFO,timeout=5) def cb_favorites(self,answer): print "cb_favorites",answer,self.current if not answer: return value = answer[1] self.config.add_item(value,self.current) self.config.write_streams() txt = "'%s' added to favourite stream list '%s'"%(self.current[0],value) self.session.open(MessageBox, txt, MessageBox.TYPE_INFO,timeout=3) #self.session.openWithCallback(self.callMyMsg, MessageBox, _("Do you want to exit the plugin?"), MessageBox.TYPE_INFO) def options_screen(self): source = self.cur_menu[1].split("::")[0] options = self.sources.options_read(source) print source if not options: self.session. open(MessageBox, "No options available for source %s (%s)"%(self.cur_menu[0],source) , MessageBox.TYPE_INFO) else: self.session.open(OptionsScreen,self) def video_download_stop(self,title): #self.activeDownloads -= 1 #self.msg("Download '%s'finished!"%title) print "video_download_stop ", title def msg2(self,msg,timeout=10,mtype = None): mtype=mtype if mtype else MessageBox.TYPE_INFO Notifications.AddPopup(text = msg, type=mtype, timeout=timeout) def msg(self,msg,timeout=10): self.session.open(MessageBox, msg, MessageBox.TYPE_INFO, timeout) ########################################################################## from Components.config import config, ConfigSubsection, ConfigYesNo,\ getConfigListEntry, ConfigSelection, ConfigNumber, ConfigDirectory,ConfigText, ConfigSubDict from Components.ConfigList import ConfigListScreen #from Screens.LocationBox import LocationBox config.plugins.playstream = ConfigSubDict() class OptionsScreen(ConfigListScreen,Screen): skin = """ """ def __init__(self, session,*args): self.session = session Screen.__init__(self, session) self.main = args[0] self.setTitle(self.main.cur_menu[0]+" options") self.source = self.main.cur_menu[1].split("::")[0] self.cfg = config.plugins.playstream self.list = [] self.options = self.main.sources.options_read(self.source) if not self.options: #self.session. open(MessageBox, "No options available for source %s (%s)"%(self.main.cur_menu[0],self.source) , MessageBox.TYPE_INFO) self.close(False,self.session) for k in self.options: self.cfg[k]=ConfigText(default=self.options[k],fixed_size=False) self.list.append(getConfigListEntry(k, self.cfg[k])) ConfigListScreen.__init__(self, self.list, session = self.session) self["key_red"] = Button(_("Cancel")) self["key_green"] = Button("Save") self["key_yellow"] = Button("") self["key_blue"] = Button("") self["setupActions"] = ActionMap(["SetupActions", "ColorActions"], { "red": self.cancel, "green": self.save, "save": self.save, "cancel": self.cancel, "ok": self.ok, }, -2) def getCurrentEntry(self): return self["config"].getCurrent()[0] def getCurrentValue(self): return str(self["config"].getCurrent()[1].getText()) def ok(self): self.save() #if self["config"].getCurrent()[1] == config.plugins.getpicons.folder: #folder = config.plugins.getpicons.folder.value #self.session.openWithCallback(self.change_dir, LocationBox,"Select Folder") #else: #def change_dir(self, folder, select=None): #if folder: ##print "change_dir to %s"%folder #config.plugins.getpicons.folder.value = folder def save(self): print "saving" #self.saveAll() for k in self.options.keys(): self.options[k]=self.cfg[k].value print "%s=%s"%(k,self.cfg[k].value) self.main.sources.options_write(self.source,self.options) self.close(True,self.session) def cancel(self): print "cancel" self.close(False,self.session)