''' SVPlayer media objects (c) 2011-2014 Jan ONDREJ (SAL) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Please, do not edit this file if you need to set configuration parameters. Copy updated parameters to config.py file. ''' import os.path, urllib2, urllib, time from copy import deepcopy from glob import glob from random import shuffle from screensaver import * from paths import * window_default_size = [] # by default don't resize geometries = [ (None, None), #(None, "auto"), # autocrop (None, "16:9"), # crop to 16:9 (None, "16:10"), # crop to 16:10 #("4:3", None), # set aspect to 4:3 #("16:9", None), # set aspect to 16:9 ] subtitle_font = "sans 18" subtitle_encoding = "CP1250" youtube_player_rows = 1 youtube_player_columns = 5 web_remote_control = None # disabled remote_guide = None # keyboard remapping key_remap = dict( KP_Insert = "0", KP_End = "1", KP_Down = "2", KP_Page_Down = "3", KP_Left = "4", KP_Begin = "5", KP_Right = "6", KP_Home = "7", KP_Up = "8", KP_Page_Up = "9", space = "p", ) # Media classes class media(): source = "URL" channel = None archive_only = False guide_only = False default_player = 'vlc' cache_ms = 180 options_default = {} options_gst = {} options_vlc = { "input-timeshift-path": recorder_path, "sub-autodetect-fuzzy": "1", # any subtitle file #"sub-autodetect-fuzzy": "3", # file matching the movie name with additional chars "freetype-rel-fontsize": "12", #"vout": "xcb_xv", # vout_sdl, xcb_xv, xcb_x11, xcb_glx, fb "network-caching": str(cache_ms), "file-caching": str(cache_ms), "equalizer-bands": "-20 -20 20 20 20 20 -20 -20 -20 -20", #"no-dts-dynrng": None, "no-a52-dynrng": None, #"no-dvdnav-menu": None, } options_mplayer = { "-zoom": None, "-subcp": None, "enca:sk": None, "-demuxer": None, "lavf": None, #"-vo": None, "xv": None, "-subcp": None, "cp1250": None, } default_deinterlace = None max_name = 40 url_format = None restart = [] background_audio = None def __init__(self, name, url, subtitles=[], lang=[], volume=1, deinterlace=None, auth=False, restart=[], background_audio=None): if len(name)>self.max_name: name = os.path.basename(urllib.unquote_plus(name)) if len(name)>self.max_name: name = urllib.unquote_plus(name)[:self.max_name] self.name = name if auth: from urlparse import urlparse from netrc import netrc pu = urlparse(url) try: nr = netrc().authenticators(pu.hostname) if nr: url = url.replace("://", "://%s:%s@" % (nr[0], nr[2]), 1) except IOError: pass if self.url_format is not None: self.url = self.url_format % url else: self.url = url self.subtitles = subtitles self.lang_priority = lang self.volume = volume if deinterlace is None: if type(url)==str and ("/VIDEO_TS" in url): # turn on deinterlace for DVDs self.deinterlace = "DEFAULT" else: self.deinterlace = self.default_deinterlace else: self.deinterlace = deinterlace self.restart = restart self.background_audio = background_audio self.update() def update(self, player=None): player = player or self.default_player if player=='mplayer': self.options = self.options_mplayer.copy() # copy of options elif player=='gst': self.options = self.options_gst.copy() # copy of options elif player=='vlc': self.options = self.options_vlc.copy() # copy of options if type(self.url)==str: if self.url.startswith("ftp://"): self.options["ftp-account"]="anonymous" self.options["ftp-user"]="anonymous" self.options["ftp-pwd"]="example@test.com" elif self.url.startswith("screen://"): self.options["screen-fps"]="25" else: self.options = self.options_default.copy() def __call__(self, source, player=None): self.update(player) return self def source_count(self): return 1 def find_archive(self): return None def find_guide(self): return None def stop(self): return class ignore_media(media): source = "IGNORED" def __init__(self, name, *args, **kw): media.__init__(self, name, "/dev/null") class multicast(media): source = "Multicast" dfault_player = "vlc" def __init__(self, name, url, group=None, deinterlace="DEFAULT", **kw): self.playlist_group = group media.__init__(self, name, url, deinterlace=deinterlace, **kw) class multicast_sanet(multicast): format = "rtp://233.10.47.%d:%d" source = "SANET" default_player = 'vlc' def __init__(self, name, ip, port=1234, deinterlace="DEFAULT", **kw): if type(ip)==int: url = self.format % (ip, port) else: url = "rtp://%s:%d/" % (ip, port) media.__init__( self, name, url, deinterlace=deinterlace, **kw ) sanet = multicast_sanet # alias class multicast_antik(multicast): source = "ANTIK" def __init__(self, name, ip, port=5004, lang=[], deinterlace="DEFAULT", **kw): url = "rtp://%s:%d" % (ip, port) media.__init__(self, name, url, lang=lang, deinterlace=deinterlace, **kw ) antik = multicast_antik # alias class antik_archive(media): source = "Archive" default_player = 'gst' #'mplayer' options_mplayer = {'-zoom': None, '-demuxer': None, 'lavf': None} archive_only = True def __init__(self, name, id): media.__init__(self, name, 'http://iptv.antik.sk/epg/archiv_epg_under_screen/date/%%d/channel/%d' % id ) class antik_archive_ftp(media): source = "Archive" default_player = 'gst' #'mplayer' options_mplayer = {'-zoom': None, '-demuxer': None, 'lavf': None} antik_ftp_host = '10.254.9.2' antik_ftp_url = 'ftp://'+antik_ftp_host+'/%s.ts' def __init__(self, name, id): media.__init__(self, name, self.ftp_download(id)) def stop(self): self.ftp = None print "FTP: exising background thread" self.bg_thread.join() def background(self): # send NOOP commands every 60 seconds to avoid closing connection while self.ftp: for i in range(60): time.sleep(0.1) if not self.ftp: return try: print "FTP: fd://%d: sending NOOP" % self.url self.ftp.voidcmd("NOOP") except: return def ftp_download(self, file_id): import ftplib, threading self.ftp = ftplib.FTP(self.antik_ftp_host) self.ftp.login() self.ftp.voidcmd('TYPE I') self.bg_thread = threading.Thread(target=self.background) self.bg_thread.start() self.sock = self.ftp.transfercmd("RETR %s.ts" % file_id) return self.sock.fileno() class guide(media): source = "Guide" guide_only = True def __init__(self, name=None): self.name = name self.url = None class dvbt(media): ''' DVB-T source Create channel configuration with: scandvb sk-Kosice > ~/.config/gstreamer-1.0/dvb-channels.conf or: w_scan -M > ~/.config/gstreamer-1.0/dvb-channels.conf ''' key_order = dict(frequency=1, bandwidth=2) def __init__(self, name, channel, program=None, deinterlace="DEFAULT", **kw): self.channel = channel self.program = program media.__init__(self, name, "dvb://%s" % urllib.quote(self.channel), deinterlace=deinterlace ) self.source = "DVB-T %dMHz" % (kw['frequency']/1000000) def sort_items(self, kw): return sorted(kw.items(), key=lambda x: self.key_order.get(x[0], 999)) def update(self, player=None): media.update(self, player) if player=='gst': self.options["channel"]="%s" % urllib.quote(self.channel) class dvbt_freq(media): key_order = dict(frequency=1, bandwidth=2) def __init__(self, name, channel, program=None, deinterlace="DEFAULT", **kw): self.channel = channel self.program = program media.__init__(self, name, "dvb-t://"+":".join([ "%s=%s" % (x.replace("_", "-"), y) for x, y in self.sort_items(kw) ]), deinterlace=deinterlace, **kw ) self.source = "DVB-T %dMHz" % (kw['frequency']/1000000) def sort_items(self, kw): return sorted(kw.items(), key=lambda x: self.key_order.get(x[0], 999)) def update(self, player=None): media.update(self, player) self.options["program"]="%d" % self.program class v4l2(media): options_vlc = media.options_vlc.copy() options_vlc.update({ ":input-slave": "alsa://hw:2,0", "v4l2-use-libv4l2": None, "v4l2-controls-reset": None, #"v4l2-audio-volume": "1" }) def __init__(self, name, freq=0, device="/dev/video0", input=0, **kw): self.freq = freq media.__init__(self, name, "v4l2://%s:tuner-frequency=%d:input=%d" % (device, freq, input), deinterlace="DEFAULT", **kw ) self.source = "V4L2 %3.2fMHz" % (freq/1000.0) class multi(media): source = "multi" def __init__(self, name, *urls, **kw): media.__init__(self, name, urls[0], **kw) # update name for guides for url in urls: if isinstance(url, guide) and url.name is None: url.name = name self.urls = urls def source_count(self): return len([ x for x in self.urls if not x.guide_only and not x.archive_only ]) def __call__(self, source, player=None): #print self.urls, self.urls[source] self.urls[source](player) return self.urls[source] def find_archive(self): for i in self.urls: if i.archive_only: return i return None def find_guide(self): for i in self.urls: if i.guide_only: return i return None def add(self, url): self.urls += (url,) def insert(self, index, url): urls = list(self.urls) urls.insert(index, url) self.urls = tuple(urls) class youtube(media): source = "YouTube" default_player = 'gst' #'mplayer' options_mplayer = { '-zoom': None, '-cookies': None, '-cookies-file': None, cookies_filename: None } class pls_playlist(multi): sources = None def __init__(self, name, *pls): multi.__init__(self, name, pls[0]) self.pls = pls self.urls = [] def __call__(self, source, player=None): self.download() return self.urls[source] def source_count(self): self.download() return len(self.urls) def download(self): if self.urls: return self.urls = [] for pls in self.pls: for row in urllib2.urlopen(pls): if row.startswith("File"): fn = row.split("=", 1)[1] p = urllib2.urlopen(fn) if 'Content-type' in p.headers \ and p.headers['Content-type'].startswith("audio/x-scpls"): # 2nd level playlist for row2l in p: if row2l.startswith("File"): fn2 = row2l.split("=", 1)[1] self.urls.append(media(self.name, fn2)) # stop processing after 1st set of URLs if self.urls: return else: self.urls.append(media(self.name, fn)) class filelist(object): def __init__(self, *dirs, **kw): self.dirs = dirs self.randomize = kw.get('random', True) def __call__(self): ret = [] for dirglob in self.dirs: for fn in glob(dirglob): ret.append(media(fn, fn)) if self.randomize: shuffle(ret) return ret class sap_list(object): def __init__(self, **kw): self.kw = kw self.list = [] def __call__(self): if self.list: return self.list from sap import search print "Searching SAP ..." for value in search(**self.kw): self.list.append(multicast(value.name(), value.url())) return self.list # Profiles class profiles(object): def __init__(self, kw): self._keys = list([x[0] for x in kw]) self._values = list([x[1] for x in kw]) def __getitem__(self, key): ret = self._values[key-1] if callable(ret): return ret() return ret def __len__(self): return len(self._keys) def name(self, key): return self._keys[key-1] # Decoding error counter class error_counter: ''' Class for counting values. Values are stored in buffer. For full buffer, first value is removed. Default value for bufsize (600) is a good choice if you are adding values every 100ms and you need a 60 seconds long buffer. ''' def __init__(self, bufsize=600): self.bufsize = bufsize self.buffer = [] self.last = 0 def reset(self): self.buffer = [] def add(self, value): if len(self.buffer)>=self.bufsize: self.buffer.pop(0) if value>=self.last: self.buffer.append(value-self.last) self.last = value def sum(self): return sum(self.buffer) def avg(self): return self.sum()/len(self.buffer) # History class svplayer_history: ''' History for typed strings. ''' def __init__(self, filename, max_length=1000): self.filename = filename self.max_length = max_length try: self.history = self.load() except IOError: self.history = [] def load(self): return [ x.strip() for x in open(self.filename, 'rt').readlines() if x.strip() ] def save(self): open(self.filename, 'wt').write( '\n'.join(self.history[:self.max_length]) ) def push(self, s): if not s.strip(): return # ignore empty strings # move this new string to top if s in self.history: self.history.remove(s) self.history.insert(0, s) self.save() def search(self, s): for key in self.history: if key.startswith(s): return key def get(self, frm, to=None): if to is None: return self.history[frm] return self.history[frm:to]