Commit [9d9afe] Maximize Restore History

[plugin.video.classiccinema] updated to version 0.8

spiff spiff 2011-06-15

added plugin.video.classiccinema/resources/lib/xbmcswift
added plugin.video.classiccinema/resources/lib/xbmcswift/__init__.py
added plugin.video.classiccinema/resources/lib/xbmcswift/plugin.py
changed plugin.video.classiccinema
changed plugin.video.classiccinema/addon.py
changed plugin.video.classiccinema/addon.xml
changed plugin.video.classiccinema/resources
changed plugin.video.classiccinema/resources/language
changed plugin.video.classiccinema/resources/language/English
changed plugin.video.classiccinema/resources/language/English/strings.xml
changed plugin.video.classiccinema/resources/lib
copied plugin.video.classiccinema/resources/lib/googlevideo.py -> plugin.video.classiccinema/resources/lib/xbmcswift/getflashvideo.py
copied plugin.video.classiccinema/resources/lib/xbmccommon.py -> plugin.video.classiccinema/resources/lib/xbmcswift/common.py
copied plugin.video.classiccinema/resources/lib/xbmcvideoplugin.py -> plugin.video.classiccinema/resources/lib/xbmcswift/extra.py
plugin.video.classiccinema/resources/lib/xbmcswift/__init__.py Diff Switch to side-by-side view
Loading...
plugin.video.classiccinema/resources/lib/xbmcswift/plugin.py Diff Switch to side-by-side view
Loading...
plugin.video.classiccinema/addon.py Diff Switch to side-by-side view
Loading...
plugin.video.classiccinema/addon.xml Diff Switch to side-by-side view
Loading...
plugin.video.classiccinema/resources/language/English/strings.xml Diff Switch to side-by-side view
Loading...
plugin.video.classiccinema/resources/lib/googlevideo.py to plugin.video.classiccinema/resources/lib/xbmcswift/getflashvideo.py
--- a/plugin.video.classiccinema/resources/lib/googlevideo.py
+++ b/plugin.video.classiccinema/resources/lib/xbmcswift/getflashvideo.py
@@ -1,4 +1,4 @@
-# Copyright 2011 Jonathan Beluch.
+# Copyright 2011 Jonathan Beluch. 
 #
 # 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
@@ -12,36 +12,91 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+from common import download_page, parse_qs, parse_url_qs, unhex
 import urllib
+from BeautifulSoup import BeautifulSoup as BS, SoupStrainer as SS
+import urlparse
 import re
-from xbmccommon import parse_url_qs, unhex
+try:
+    import json
+except ImportError:
+    import simplejson as json
 
-def get_flv_url(docid=None, url=None):
-    '''Takes a docid id from a google video page url, or takes a complete url 
-    to a googlevideo swf and returns the url to the actual video resource.
-    Returns None if no match is found.
-    
-    >>> get_flv_url(url='http://video.google.com/googleplayer.swf?docid=-7205983324127064982&hl=en&fs=true')
-    'http://v19.lscache1.googlevideo.com/videoplayback?id=9aced06ebda81e9a&itag=5&begin=0&ip=0.0.0.0&ipbits=0&expire=1294349447&sparams=ip,ipbits,expire,id,itag&signature=0F0871585D48C2ED310571CBA37E1D31F9283020.39A632C3EFFA49544EB2F09391AACDD613B5DB7A&key=ck1'
-    
-    >>> get_flv_url(docid='-7205983324127064982')
-    'http://v19.lscache1.googlevideo.com/videoplayback?id=9aced06ebda81e9a&itag=5&begin=0&ip=0.0.0.0&ipbits=0&expire=1294349447&sparams=ip,ipbits,expire,id,itag&signature=73F88621DCD5386AC8E215ED6FB3C9A50D113572.4EB3FB28324BA65AD7AEA70A174D067765D05297&key=ck1'
-    ''' 
+'''
+This module is meant to abstract the parsing of flash video URLs out of plugins.
+
+Each class represents a video site and should implement a @staticmethod named
+get_flashvide_url. The method should take 1 argument, a string, usually corresponding
+to HTML source code. The method should return a url for a video resource or None if
+the page wasn't able to be parsed.
+'''
+
+def get_flashvideo_url(src=None, url=None):
+    if not url and not src:
+        print 'At least src or url required.'
+
     if url:
+        src = download_page(url)
+
+    #there are 2 kinds of videos on the site, google video and archive.org
+    if src.find('googleplayer') > 0:
+        flash_url = GoogleVideo.get_flashvideo_url(src)
+    elif src.find('flowplayer') > 0:
+        flash_url = ArchiveVideo.get_flashvideo_url(src)
+    else:
+        print 'no handler implementd for this url.'
+
+    return flash_url
+
+class GoogleVideo(object):
+    @staticmethod
+    def get_flashvideo_url(src):
+        embed_tags = BS(src, parseOnlyThese=SS('embed'))
+        url = embed_tags.find('embed')['src']
         docid = parse_url_qs(url).get('docid')
-    url = 'http://video.google.com/videoplay?docid=%s&hl=en' % docid
+        url = 'http://video.google.com/videoplay?docid=%s&hl=en' % docid
 
-    #load the googlevideo page for a given docid or googlevideo swf url
-    src = urllib.urlopen(url).read()
-    flvurl_pattern = re.compile(r"preview_url:'(.+?)'")
-    m = flvurl_pattern.search(src)
-    if not m:
-        return
-    previewurl = m.group(1)
+        #load the googlevideo page for a given docid or googlevideo swf url
+        src = download_page(url)
+        flvurl_pattern = re.compile(r"preview_url:'(.+?)'")
+        m = flvurl_pattern.search(src)
+        if not m:
+            return
+        previewurl = m.group(1)
 
-    #replace hex things
-    # videoUrl\x3dhttp -> videoUrl=http
-    previewurl = unhex(previewurl)
-    #parse querystring and return the videoUrl
-    params = parse_url_qs(previewurl)
-    return urllib.unquote_plus(params['videoUrl'])
+        #replace hex things
+        # videoUrl\x3dhttp -> videoUrl=http
+        previewurl = unhex(previewurl)
+        #parse querystring and return the videoUrl
+        params = parse_url_qs(previewurl)
+        return urllib.unquote_plus(params['videoUrl'])
+
+class ArchiveVideo(object):
+    @staticmethod
+    def get_flashvideo_url(src):
+        if src.find('http://www.archive.org/flow/flowplayer.commercial-3.2.1.swf') > -1:
+            print src.find('http://www.archive.org/flow/flowplayer.commercial-3.2.1.swf')
+            return ArchiveVideo.swf_3_21(src)
+        elif src.find('http://www.archive.org/flow/flowplayer.commercial-3.0.5.swf') > -1:
+            return ArchiveVideo.swf_3_05(src)
+        else:
+            print 'Unknown swf version for ArchiveVideo.'
+        return None
+
+    @staticmethod
+    def swf_3_05(src):
+        embed_tags = BS(src, parseOnlyThese=SS('embed'))
+        flashvars = embed_tags.find('embed')['flashvars']
+        obj = json.loads(flashvars.split('=', 1)[1].replace("'", '"'))
+        path = obj['playlist'][1]['url'] 
+        return path
+
+    @staticmethod
+    def swf_3_21(src):
+        embed_tags = BS(src, parseOnlyThese=SS('embed'))
+        flashvars = embed_tags.find('embed')['flashvars']
+        obj = json.loads(flashvars.split('=', 1)[1].replace("'", '"'))
+        base_url = obj['clip']['baseUrl']
+        path = obj['playlist'][1]['url'] 
+        return urlparse.urljoin(base_url, path)
+
plugin.video.classiccinema/resources/lib/xbmccommon.py to plugin.video.classiccinema/resources/lib/xbmcswift/common.py
--- a/plugin.video.classiccinema/resources/lib/xbmccommon.py
+++ b/plugin.video.classiccinema/resources/lib/xbmcswift/common.py
@@ -23,6 +23,13 @@
 from urlparse import urlparse
 import pickle
 
+def download_page(url, data=None):
+    # Must check cache using httplib2 here!
+    u = urllib2.urlopen(url, data)
+    r = u.read()
+    u.close()
+    return r
+
 def parse_url_qs(url, pickled_fragment=False):
     '''Returns a dict of key/vals parsed from a query string.  If
     pickled_fragment=True, the method unpickles python objects stored in the
plugin.video.classiccinema/resources/lib/xbmcvideoplugin.py to plugin.video.classiccinema/resources/lib/xbmcswift/extra.py
--- a/plugin.video.classiccinema/resources/lib/xbmcvideoplugin.py
+++ b/plugin.video.classiccinema/resources/lib/xbmcswift/extra.py
@@ -1,176 +1,107 @@
-# Copyright 2011 Jonathan Beluch.
-#
-# 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 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-import urlparse
-import asyncore, socket
-import os
-import xbmc
-import xbmcgui
-import xbmcplugin
-import xbmcaddon
-import sys
-import pickle
-from urllib import quote_plus, unquote_plus, urlencode
-from cStringIO import StringIO
-from xbmccommon import XBMCDialogCancelled, parse_url_qs, XBMCVideoPluginException
-from urlparse import urlparse
+import sys as _sys
+from operator import itemgetter as _itemgetter
+from keyword import iskeyword as _iskeyword
+def namedtuple(typename, field_names, verbose=False):
+    """Returns a new subclass of tuple with named fields.
 
-class XBMCVideoPluginHandler(object):
-    '''This is a base class for a handler.  Subclass this and define
-    a run method.'''
-    def __init__(self, argv0, argv1, argsdict):
-        self.argv0 = argv0
-        self.argv1 = int(argv1)
-        self.args = argsdict
+    >>> Point = namedtuple('Point', 'x y')
+    >>> Point.__doc__                   # docstring for the new class
+    'Point(x, y)'
+    >>> p = Point(11, y=22)             # instantiate with positional args or keywords
+    >>> p[0] + p[1]                     # indexable like a plain tuple
+    33
+    >>> x, y = p                        # unpack like a regular tuple
+    >>> x, y
+    (11, 22)
+    >>> p.x + p.y                       # fields also accessable by name
+    33
+    >>> d = p._asdict()                 # convert to a dictionary
+    >>> d['x']
+    11
+    >>> Point(**d)                      # convert from a dictionary
+    Point(x=11, y=22)
+    >>> p._replace(x=100)               # _replace() is like str.replace() but targets named fields
+    Point(x=100, y=22)
 
-    def run(self):
-        raise NotImplementedError
-         
+    """
 
-class XBMCVideoPlugin(object):
-    '''This is a class to help handle routine tasks for a video plugin
-    such as adding directories and/or movies to the UI.'''
-    
-    def __init__(self, modes, plugin_id=None, default_handler=None, plugin_name='XBMC Video Plugin'):
-        self.plugin_id = plugin_id
-        self.plugin_name = plugin_name
-        self.addon = xbmcaddon.Addon(id=self.plugin_id)
-        self.dp = None
-        #set the default mode, when the plugin is first called, there will be no qs arguments
-        #use user_specified default_handler, else pick the first handler in the modes list
-        self.default_handler = default_handler or modes[0][1] 
-        self.modes = dict(modes)
+    # Parse and validate the field names.  Validation serves two purposes,
+    # generating informative error messages and preventing template injection attacks.
+    if isinstance(field_names, basestring):
+        field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas
+    field_names = tuple(map(str, field_names))
+    for name in (typename,) + field_names:
+        # pyton 2.4 does not have all keyword
+        for c in name:
+            if not c.isalnum() and c != '_':
+                raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name)
 
-        #parse command line parameters into a dictionary
-        self.argv0 = sys.argv[0]
-        self.argv1 = int(sys.argv[1])
+        #[raise ValueError('Type names and field names cannot be a keyword: %r' % name) for c in name if not c.isalnum() or c=='_']
+        
+        #if not all(c.isalnum() or c=='_' for c in name):
+            #raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name)
+        if _iskeyword(name):
+            raise ValueError('Type names and field names cannot be a keyword: %r' % name)
+        if name[0].isdigit():
+            raise ValueError('Type names and field names cannot start with a number: %r' % name)
+    seen_names = set()
+    for name in field_names:
+        if name.startswith('_'):
+            raise ValueError('Field names cannot start with an underscore: %r' % name)
+        if name in seen_names:
+            raise ValueError('Encountered duplicate field name: %r' % name)
+        seen_names.add(name)
 
-        #parse params from qs, also include the pickled fragment
-        self.params = parse_url_qs(sys.argv[2], pickled_fragment=True)
+    # Create and fill-in the class template
+    numfields = len(field_names)
+    argtxt = repr(field_names).replace("'", "")[1:-1]   # tuple repr without parens or quotes
+    reprtxt = ', '.join('%s=%%r' % name for name in field_names)
+    dicttxt = ', '.join('%r: t[%d]' % (name, pos) for pos, name in enumerate(field_names))
+    template = '''class %(typename)s(tuple):
+        '%(typename)s(%(argtxt)s)' \n
+        __slots__ = () \n
+        _fields = %(field_names)r \n
+        def __new__(_cls, %(argtxt)s):
+            return _tuple.__new__(_cls, (%(argtxt)s)) \n
+        @classmethod
+        def _make(cls, iterable, new=tuple.__new__, len=len):
+            'Make a new %(typename)s object from a sequence or iterable'
+            result = new(cls, iterable)
+            if len(result) != %(numfields)d:
+                raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result))
+            return result \n
+        def __repr__(self):
+            return '%(typename)s(%(reprtxt)s)' %% self \n
+        def _asdict(t):
+            'Return a new dict which maps field names to their values'
+            return {%(dicttxt)s} \n
+        def _replace(_self, **kwds):
+            'Return a new %(typename)s object replacing specified fields with new values'
+            result = _self._make(map(kwds.pop, %(field_names)r, _self))
+            if kwds:
+                raise ValueError('Got unexpected field names: %%r' %% kwds.keys())
+            return result \n
+        def __getnewargs__(self):
+            return tuple(self) \n\n''' % locals()
+    for i, name in enumerate(field_names):
+        template += '        %s = _property(_itemgetter(%d))\n' % (name, i)
+    if verbose:
+        print template
 
-    def getls(self, stringid):
-        return self.addon.getLocalizedString(stringid)
-            
-    def set_resolved_url(self, url):
-        li = xbmcgui.ListItem(path=url)
-        xbmcplugin.setResolvedUrl(self.argv1, True, li)
+    # Execute the template string in a temporary namespace and
+    # support tracing utilities by setting a value for frame.f_globals['__name__']
+    namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename,
+                     _property=property, _tuple=tuple)
+    try:
+        exec template in namespace
+    except SyntaxError, e:
+        raise SyntaxError(e.message + ':\n' + template)
+    result = namespace[typename]
 
-    def getString(self, strid):
-        return self.addon.getLocalizedString(strid)
+    # For pickling to work, the __module__ variable needs to be set to the frame
+    # where the named tuple is created.  Bypass this step in enviroments where
+    # sys._getframe is not defined (Jython for example).
+    if hasattr(_sys, '_getframe'):
+        result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
 
-    def special_url(self, argv0, params):
-        '''Creates a special url resolving this XBMC plugin.  If a val in the params
-        dict is not an instance of basestring, it is added to a special dict which will
-        then be pickled and placed in the fragment identifier section of the url'''
-        pickled = {}
-        qs_params = {}
-        for key, val in params.items():
-            if not(isinstance(val, basestring)):
-                #not a string so pickle it
-                pickled[key] = val
-            else:
-                qs_params[key] = val
-        #now pickle the pickle dict and add to qs
-        return '%s?%s#%s' % (argv0, urlencode(qs_params), quote_plus(pickle.dumps(pickled)))
-    
-    def make_directory_item(self, d, isfolder=False, make_plugin_url=False):
-        url = d.get('url')
-        if make_plugin_url:
-            url = self.special_url(self.argv0, d)
-
-        li = xbmcgui.ListItem(d.get('name'))
-
-        #Ensure there are items in the info dict or XBMC throws an error
-        if 'info' in d.keys() and len(d['info']) > 1: 
-            li.setInfo('video', d.get('info'))
-
-        if 'icon' in d.keys(): li.setIconImage(d.get('icon'))
-        if 'tn' in d.keys(): li.setThumbnailImage(d.get('tn'))
-
-        if not isfolder:
-            li.setProperty('IsPlayable', 'true')
-
-        return (url, li, isfolder)
-
-    def add_resolvable_dirs(self, dirs, end=True):
-        _dirs = [self.make_directory_item(d, isfolder=False, make_plugin_url=True) for d in dirs]
-        xbmcplugin.addDirectoryItems(self.argv1, _dirs, len(_dirs))
-        if end == True: 
-            xbmcplugin.endOfDirectory(self.argv1, cacheToDisc=True)
-        
-    def add_videos(self, lis, end=True):
-        """Takes a list of directory items which will be added as
-        videos in the XBMC UI.  Each directory item is a dictionary
-        containing the following key/value pairs:
-            name = title of the video
-            url = url of the video
-            info = a dict object with key/val pairs. Info can be found
-                in the xbmc documentation (optional)
-            icon = url to an icon (optional)
-            tn = url to a thumbnail (optional)
-        The second parameter the function takes is 'end'.  This
-        simply defines whether or not to call 
-        xbmcplugin.endOfDirectory()
-        """
-        #needs to be tested
-        #_lis = [self._make_directory_item(li, False) for li in lis]
-        _lis = [self.make_directory_item(li, isfolder=True, make_plugin_url=True) for li in lis]
-        xbmcplugin.addDirectoryItems(self.argv1, _lis, len(_lis))
-        if end == True: 
-            xbmcplugin.endOfDirectory(self.argv1, cacheToDisc=True)   
-    
-    def add_dirs(self, dirs, end=True):
-        """Takes a list of directory items which will be added as
-        folders in the XBMC UI.  Each directory item is a dictionary
-        containing the following key/value pairs:
-            name = title of the list item 
-            url = url of the folder contents (usu a link back to the plugin)
-            info = a dict object with key/val pairs. Info can be found
-                in the xbmc documentation (optional)
-            icon = url to an icon (optional)
-            tn = url to a thumbnail (optional)
-        The second parameter the function takes is 'end'.  This
-        simply defines whether or not to call 
-        xbmcplugin.endOfDirectory()
-        """
-        #_dirs = [self._make_directory_item(d, True) for d in dirs]
-        _dirs = [self.make_directory_item(d, isfolder=True, make_plugin_url=True) for d in dirs]
-        xbmcplugin.addDirectoryItems(self.argv1, _dirs, len(_dirs))
-        if end == True: 
-            xbmcplugin.endOfDirectory(self.argv1, cacheToDisc=True)
-               
-    def run(self):
-        #use the mode parameter if available, if not use the default handler
-        handler = self.default_handler
-        mode = self.params.get('mode')
-        if mode:
-            #verify mode is in the self.modes dict, should we fail silently here and use default?
-            assert mode in self.modes.keys(), 'Specified mode %s not found in self.modes' % mode
-            handler = self.modes[mode]
-
-        #create instance of the current handler and call its run method
-        #current_handler = self.modes[mode](self.argv0, self.argv1, self.params)
-        current_handler = handler(self.argv0, self.argv1, self.params)
-        try:
-            current_handler.run()
-        #if there is any kind of non-recoverable error in the process of playing the video
-        #raise an instance of XBMCVideoPluginException and a dialog box will be displayed 
-        #informing the user
-        #python 2.4 except clause syntax
-        except XBMCVideoPluginException, e:
-            dialog = xbmcgui.Dialog()
-            ok = dialog.ok('XBMC', e.value)
-        except XBMCDialogCancelled:
-            #user cancelled the dialog progress
-            pass
+    return result