Ticket #296: vdrstream.patch

File vdrstream.patch, 18.4 kB (added by Wallenstein, 2 years ago)

vdrStream Plugin Patch (based on 0.6.6.x)

  • Coherence.egg-info/PKG-INFO

    old new  
    2020         
    2121        New in this 0.6.6.3 - the Red-Nosed Reindeer Revolutions - release 
    2222         
    23         * new MediaServer backends that allow access to 
    24         * Banshee - exports audio and video files from Banshees media db (http://banshee-project.org/) 
    25         * FeedStore - a MediaServer serving generic RSS feeds 
    26         * Playlist - exposes the list of video/audio streams from a m3u playlist (e.g. web TV listings published by french ISPs such as Free, SFR...) 
    27         * YAMJ - serves the movie/TV series data files and metadata from a given YAMJ (Yet Another Movie Jukebox) library (http://code.google.com/p/moviejukebox/) 
    28         * updates on Mirabeau - our "UPnP over XMPP" bridge 
    29         * simplifications in the D-Bus API 
    30         * a first implementation of an JSON/REST API 
    31         * advancements of the GStreamer MediaRenderer, supporting now GStreamers playbin2 
    32         * upgrade of the DVB-Daemon MediaServer 
    33         * refinements in the transcoding section, having now the choice to use GStreamer pipelines or external processes like mencoder 
    34         * more 'compatibility' improvements for different devices (e.g. Samsung TVs or Apache Felix) 
    35         * and - as every time - the usual bugfixes and enhancements 
     23         * new MediaServer backends that allow access to 
     24           * Banshee - exports audio and video files from Banshees media db (http://banshee-project.org/) 
     25           * FeedStore - a MediaServer serving generic RSS feeds 
     26           * Playlist - exposes the list of video/audio streams from a m3u playlist (e.g. web TV listings published by french ISPs such as Free, SFR...) 
     27           * YAMJ - serves the movie/TV series data files and metadata from a given YAMJ (Yet Another Movie Jukebox) library (http://code.google.com/p/moviejukebox/) 
     28         * updates on Mirabeau - our "UPnP over XMPP" bridge 
     29         * simplifications in the D-Bus API 
     30         * a first implementation of an JSON/REST API 
     31         * advancements of the GStreamer MediaRenderer, supporting now GStreamers playbin2 
     32         * upgrade of the DVB-Daemon MediaServer 
     33         * refinements in the transcoding section, having now the choice to use GStreamer pipelines or external processes like mencoder 
     34         * more 'compatibility' improvements for different devices (e.g. Samsung TVs or Apache Felix) 
     35         * and - as every time - the usual bugfixes and enhancements 
    3636         
    3737        Kudos go to: 
    3838         
    39         * Benjamin (lightyear) Kampmann, 
    40         * Charlie (porthose) Smotherman 
    41         * Dominik (schrei5) Ruf, 
    42         * Frank (dev) Scholz, 
    43         * Friedrich (frinring) Kossebau, 
    44         * Jean-Michel (jmsizun) Sizun, 
    45         * Philippe (philn) Normand, 
    46         * Sebastian (sebp) Poelsterl, 
    47         * Zaheer (zaheerm) Merali 
     39         * Benjamin (lightyear) Kampmann, 
     40         * Charlie (porthose) Smotherman 
     41         * Dominik (schrei5) Ruf, 
     42         * Frank (dev) Scholz, 
     43         * Friedrich (frinring) Kossebau, 
     44         * Jean-Michel (jmsizun) Sizun, 
     45         * Philippe (philn) Normand, 
     46         * Sebastian (sebp) Poelsterl, 
     47         * Zaheer (zaheerm) Merali 
    4848         
    4949         
    5050         
  • Coherence.egg-info/entry_points.txt

    old new  
    2020        YouTubeStore = coherence.backends.youtube_storage:YouTubeStore 
    2121        MiroGuideStore = coherence.backends.miroguide_storage:MiroGuideStore 
    2222        ITVStore = coherence.backends.itv_storage:ITVStore 
     23        VDRStream = coherence.backends.vdr_stream:VDRStream 
    2324        PicasaStore = coherence.backends.picasa_storage:PicasaStore 
    2425        TestStore = coherence.backends.test_storage:TestStore 
    2526        PlaylistStore = coherence.backends.playlist_storage:PlaylistStore 
  • setup.py

    old new  
    132132        YouTubeStore = coherence.backends.youtube_storage:YouTubeStore 
    133133        MiroGuideStore = coherence.backends.miroguide_storage:MiroGuideStore 
    134134        ITVStore = coherence.backends.itv_storage:ITVStore 
     135        VDRStream = coherence.backends.vdr_stream:VDRStream 
    135136        PicasaStore = coherence.backends.picasa_storage:PicasaStore 
    136137        TestStore = coherence.backends.test_storage:TestStore 
    137138        PlaylistStore = coherence.backends.playlist_storage:PlaylistStore 
  • coherence/upnp/core/DIDLLite.py

    old new  
    181181    if content_format == 'video/mpeg': 
    182182        additional_info = ['DLNA.ORG_PN=MPEG_PS_PAL']+simple_dlna_tags 
    183183    if content_format == 'video/mpegts': 
    184         additional_info = ['DLNA.ORG_PN=MPEG_TS_PAL']+simple_dlna_tags 
     184        # Change for Sony Bravia: Does nit support simple MPEG_TS_PAL 
     185        additional_info = ['DLNA.ORG_PN==MPEG_TS_SD_EU_ISO']+simple_dlna_tags 
    185186        content_format = 'video/mpeg' 
    186187    if content_format in ['video/mp4','video/x-m4a']: 
    187188        additional_info = ['DLNA.ORG_PN=AVC_TS_BL_CIF15_AAC']+simple_dlna_tags 
  • coherence/upnp/core/utils.py

    old new  
    418418        clientFactory = self.proxyClientFactoryClass( 
    419419            request.method, rest, request.clientproto, 
    420420            request.getAllHeaders(), request.content.read(), request) 
    421         self.reactor.connectTCP(self.host, self.port, clientFactory) 
     421        self.connector = self.reactor.connectTCP(self.host, self.port, clientFactory) 
    422422        return server.NOT_DONE_YET 
    423423 
    424424    def resetTarget(self,host,port,path,qs=''): 
  • coherence/backends/vdr_stream.py

    old new  
     1# Licensed under the MIT license 
     2# http://opensource.org/licenses/mit-license.php 
     3 
     4# Backend to retrieve the live video streams from VDR TV 
     5 
     6# Copyright 2007, Frank Scholz <coherence@beebits.net> 
     7# Copyright 2008,2009 Jean-Michel Sizun <jmDOTsizunATfreeDOTfr> 
     8# Copyright 2010 Michael Breu <Michael.BreuATarctis.at> 
     9 
     10from twisted.internet import defer,reactor 
     11from twisted.web import server 
     12 
     13from coherence.upnp.core import utils 
     14 
     15from coherence.upnp.core import DIDLLite 
     16 
     17from coherence.extern.simple_plugin import Plugin 
     18 
     19from coherence import log 
     20from coherence.backend import BackendItem, BackendStore 
     21 
     22from urlparse import urlsplit 
     23 
     24import zlib 
     25import re 
     26import traceback 
     27import time 
     28 
     29from coherence.backend import BackendStore,BackendItem 
     30 
     31 
     32ROOT_CONTAINER_ID = 0 
     33 
     34VDR_WS_URL = 'http://arctisserver2:3000/TS/' 
     35VIDEO_MIMETYPE = 'video/mpegts' 
     36 
     37def funcInfo(object, spacing=10, collapse=1):  
     38    """Print methods and doc strings. 
     39     
     40    Takes module, class, list, dictionary, or string.""" 
     41    methodList = [method for method in dir(object) if callable(getattr(object, method))] 
     42    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s) 
     43    print "\n".join(["%s %s" % 
     44                      (method.ljust(spacing), 
     45                       processFunc(str(getattr(object, method).__doc__))) 
     46                     for method in methodList]) 
     47 
     48 
     49class ProxyStream(utils.ReverseProxyUriResource, log.Loggable): 
     50    logCategory = 'vdr' 
     51 
     52    stream_url = None 
     53 
     54    def __init__(self, uri): 
     55#        self.info("ProxyStream created") 
     56        self.stream_url = None 
     57        utils.ReverseProxyUriResource.__init__(self, uri) 
     58 
     59    def requestFinished(self, result): 
     60        """ self.connection is set in utils.ReverseProxyResource.render """ 
     61        self.info("ProxyStream requestFinished") 
     62#        funcInfo(self) 
     63        self.info("ProxyStream reactor") 
     64#        funcInfo(self.reactor) 
     65#        self.reactor.removeAll() 
     66#        self.reactor.disconnectAll() 
     67        if self.connector is not None: 
     68            self.info("disconnect") 
     69            self.connector.disconnect() 
     70 
     71    def render(self, request): 
     72        self.info("ProxyStream rendering") 
     73 
     74        if request.clientproto == 'HTTP/1.1': 
     75            self.info("ProxyStream clientProto is HTTP/1.1") 
     76            d = request.notifyFinish() 
     77            d.addBoth(self.requestFinished) 
     78        else: 
     79            self.info("ProxyStream clientProto is not HTTP/1.1") 
     80            d = request.notifyFinish() 
     81            d.addBoth(self.requestFinished) 
     82        return utils.ReverseProxyUriResource.render(self, request) 
     83 
     84 
     85class Container(BackendItem): 
     86 
     87    logCategory = 'vdr' 
     88 
     89    def __init__(self, id, store, parent_id, title): 
     90        self.url = store.urlbase+str(id) 
     91        self.parent_id = parent_id 
     92        self.id = id 
     93        self.name = title 
     94        self.mimetype = 'directory' 
     95        self.update_id = 0 
     96        self.children = [] 
     97        self.store = store 
     98 
     99        self.item = DIDLLite.Container(self.id, self.parent_id, self.name) 
     100        self.item.childCount = 0 
     101 
     102        self.sorted = False 
     103        self.timestamp = time.time() 
     104 
     105    def add_child(self, child): 
     106        id = child.id 
     107        if isinstance(child.id, basestring): 
     108            _,id = child.id.split('.') 
     109        if self.children is None: 
     110            self.children = [] 
     111        self.children.append(child) 
     112        self.item.childCount += 1 
     113        self.sorted = False 
     114        if self.id == ROOT_CONTAINER_ID: 
     115          self.debug('adding ' + child.name) 
     116 
     117    def get_children(self, start=0, end=0): 
     118        ''' 
     119        if self.sorted == False: 
     120            def childs_sort(x,y): 
     121                r = cmp(x.name,y.name) 
     122                return r 
     123 
     124            self.children.sort(cmp=childs_sort) 
     125            self.sorted = True 
     126        ''' 
     127         
     128        if self.id == ROOT_CONTAINER_ID: 
     129          self.update() 
     130 
     131        if end != 0: 
     132            return self.children[start:end] 
     133        return self.children[start:] 
     134 
     135    def get_child_count(self): 
     136        if self.id == ROOT_CONTAINER_ID: 
     137          self.update() 
     138        return self.internal_child_count() 
     139 
     140    def internal_child_count(self): 
     141        if self.children is None: 
     142          return 0 
     143        return len(self.children) 
     144 
     145    def get_path(self): 
     146        return self.url 
     147 
     148    def get_item(self): 
     149        return self.item 
     150 
     151    def get_name(self): 
     152        return self.name 
     153 
     154    def get_id(self): 
     155        return self.id 
     156 
     157    def update(self): 
     158        self.debug('get_children for rootcontainer called') 
     159        cachetime = 5.0  # seconds 
     160        if self.timestamp + cachetime < time.time() or self.internal_child_count()==0: 
     161             self.debug('update children') 
     162             self.timestamp = time.time() 
     163             self.store.retrieveList(self) 
     164 
     165 
     166class VDRItem(BackendItem): 
     167    logCategory = 'vdr' 
     168     
     169    def __init__(self, store, id, obj, parent): 
     170        self.parent = parent 
     171        self.id = id 
     172        self.name = obj.get('name') 
     173        self.mimetype = obj.get('mimetype') 
     174        self.description = None 
     175        self.date = None 
     176        self.item = None 
     177        self.duration = None 
     178        self.store = store 
     179        self.url = self.store.urlbase + str(self.id) 
     180        self.stream_url = obj.get('url') 
     181        self.location = ProxyStream(self.stream_url) 
     182 
     183    def get_item(self): 
     184        if self.item == None: 
     185            _,host_port,_,_,_ = urlsplit(self.store.urlbase) 
     186            if host_port.find(':') != -1: 
     187              host,port = tuple(host_port.split(':')) 
     188            else: 
     189              host = host_port 
     190 
     191 
     192            self.item = DIDLLite.VideoItem(self.id, self.parent.id, self.name) 
     193            self.item.description = self.description 
     194            self.item.date = self.date 
     195            res = DIDLLite.Resource(self.url, 'http-get:*:%s:*' % self.mimetype)  
     196            res.duration = self.duration 
     197            #res.size = 0 #None 
     198            self.item.res.append(res) 
     199 
     200            res = DIDLLite.Resource(self.url, 'internal:%s:%s:*' % self.mimetype)  
     201            res.duration = self.duration 
     202            #res.size = 0 #None 
     203            self.item.res.append(res) 
     204        return self.item 
     205 
     206    def get_path(self): 
     207        return self.url 
     208 
     209 
     210class VDRStream(BackendStore): 
     211 
     212    logCategory = 'vdr' 
     213 
     214    implements = ['MediaServer'] 
     215 
     216    description = ('VDR Streaming Server', 'Exposes the VDR Streams', None) 
     217 
     218    options = [{'option':'name', 'text':'Server Name:', 'type':'string','default':'my media','help': 'the name under this MediaServer shall show up with on other UPnP clients'}, 
     219       {'option':'version','text':'UPnP Version:','type':'int','default':2,'enum': (2,1),'help': 'the highest UPnP version this MediaServer shall support','level':'advance'}, 
     220       {'option':'uuid','text':'UUID Identifier:','type':'string','help':'the unique (UPnP) identifier for this MediaServer, usually automatically set','level':'advance'},     
     221       {'option':'streamdevURL','text':'VDR streamdev server URL','type':'string', 'default':VDR_WS_URL} 
     222    ] 
     223 
     224    def __init__(self, server, **kwargs): 
     225        BackendStore.__init__(self,server,**kwargs) 
     226        self.next_id = 1000 
     227        self.config = kwargs 
     228        self.name = kwargs.get('name','vdr') 
     229 
     230        self.update_id = 0 
     231        self.store = {} 
     232 
     233        self.wmc_mapping = {'4': 1000} 
     234 
     235 
     236        self.VDR_ws_url = self.config.get('streamdevURL',VDR_WS_URL) 
     237 
     238        self.init_completed() 
     239 
     240 
     241    def __repr__(self): 
     242        return self.__class__.__name__ 
     243 
     244    def storeItem(self, parent, item, id): 
     245        self.store[id] = item 
     246        parent.add_child(item) 
     247 
     248    def appendGenre( self, genre, parent): 
     249        id = self.getnextID() 
     250        item = Container(id, self, -1, genre) 
     251        self.storeItem(parent, item, id) 
     252        return item 
     253 
     254    def appendFeed( self, obj, parent): 
     255        id = self.getnextID() 
     256        item = VDRItem(self, id, obj, parent) 
     257        self.storeItem(parent, item, id) 
     258        return item 
     259 
     260    def len(self): 
     261        return len(self.store) 
     262 
     263    def get_by_id(self,id): 
     264        if isinstance(id, basestring): 
     265            id = id.split('@',1) 
     266            id = id[0] 
     267        try: 
     268            return self.store[int(id)] 
     269        except (ValueError,KeyError): 
     270            pass 
     271        return None 
     272 
     273    def getnextID(self): 
     274        ret = self.next_id 
     275        self.next_id += 1 
     276        return ret 
     277 
     278    def upnp_init(self): 
     279        self.current_connection_id = None 
     280 
     281        if self.server: 
     282            self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', 
     283                                                                    ['http-get:*:%s:*' % VIDEO_MIMETYPE, 
     284                                                                     'internal:%s:%s:*' % (self.server.coherence.hostname,VIDEO_MIMETYPE), 
     285                                                                     ], 
     286                                                                   default=True) 
     287        rootItem = Container(ROOT_CONTAINER_ID,self,-1, self.name) 
     288        self.store[ROOT_CONTAINER_ID] = rootItem 
     289        self.retrieveList_attemptCount = 0 
     290        self.retrieveList(rootItem) 
     291 
     292    def retrieveList(self, rootContainer): 
     293        self.info("Retrieving VDR TV listing...") 
     294 
     295        def got_page(result): 
     296            if self.retrieveList_attemptCount == 0: 
     297                self.info("Connection to VDR streamdev server successful for TV listing") 
     298            else: 
     299                self.warning("Connection to VDR streamdev server successful for TV listing after %d attempts." % self.retrieveList_attemptCount) 
     300            result = result[0] 
     301 
     302            matches = re.findall('<h2>([^<]+)</h2>', result) 
     303            self.info("got matches %r", matches) 
     304 
     305#               self.info("got match %r", match.group(1)) 
     306#               self.info("  got matches %r", match.group(2)) 
     307 
     308            genres = [] 
     309            channels = [] 
     310 
     311            for genrematch in re.finditer('<div class="group"><h2>([^<]+)</h2>(.*?)</div>', result, re.DOTALL): 
     312               genre = genrematch.group(1) 
     313#               self.info("got genre %r", genre) 
     314               for match in re.finditer('<a href="([^ ]+?)" vod  tvid="\d+">(.*?)</a>', genrematch.group(2), re.DOTALL): 
     315                   channel_id = match.group(1) 
     316                   name = match.group(2) 
     317#                   self.info("got channel URL %r", channel_id) 
     318#                   self.info("  got channel name %r", name) 
     319      
     320 
     321                   mimetype = VIDEO_MIMETYPE 
     322                   url = (self.VDR_ws_url+'%s') % (channel_id) 
     323 
     324                   if genres.count(genre) == 0: 
     325                       genres.append(genre) 
     326 
     327                   channel = {'name':name, 
     328                              'channel_id':channel_id, 
     329                              'mimetype':mimetype, 
     330                              'id':channel_id, 
     331                              'url':url, 
     332                              'genre':genre } 
     333 
     334                   channels.append(channel) 
     335 
     336            # reset childrens             
     337            rootContainer.children=[] 
     338 
     339            genreItems = {} 
     340            for genre in genres: 
     341                genreItem = self.appendGenre(genre, rootContainer) 
     342                genreItems[genre] = genreItem 
     343 
     344            for channel in channels: 
     345                genre = channel.get('genre') 
     346                parentItem = genreItems[genre] 
     347#                self.info("appending to %r channel %r with URL %r", genre, channel.get('name'), channel.get('url')) 
     348                self.appendFeed({'name':channel.get('name'), 
     349                                    'mimetype':channel['mimetype'], 
     350                                    'id':channel.get('channel_id'), 
     351                                    'url':channel.get('url')}, 
     352                            parentItem) 
     353 
     354 
     355        def got_error(error): 
     356            self.warning("Connection to VDR streamdev service failed. Will retry in 5s!") 
     357            self.debug("%r", error.getTraceback()) 
     358            # will retry later 
     359            self.retrieveList_attemptCount += 1 
     360            reactor.callLater(5, self.retrieveList, parent=rootContainer) 
     361             
     362        d = utils.getPage(self.VDR_ws_url) 
     363        d.addCallbacks(got_page, got_error)