From 2dd34be9501df101b4fe517187a176bd7550dbce Mon Sep 17 00:00:00 2001 From: Arne Bultman Date: Mon, 26 Dec 2011 12:45:54 +0100 Subject: [PATCH] Initial version --- .gitignore | 2 + addon.xml | 18 + default.py | 200 ++++ icon.png | Bin 0 -> 12816 bytes resources/language/english/strings.xml | 41 + resources/lib/brlog.py | 32 + resources/lib/elementtree/ElementPath.py | 196 ++++ resources/lib/elementtree/ElementTree.py | 1254 ++++++++++++++++++++++ resources/lib/elementtree/__init__.py | 30 + resources/lib/file.py | 6 + resources/lib/makemkv.py | 148 +++ resources/lib/mkvparser.py | 56 + resources/lib/settings.py | 86 ++ resources/settings.xml | 17 + service.py | 41 + 15 files changed, 2127 insertions(+) create mode 100644 .gitignore create mode 100644 addon.xml create mode 100644 default.py create mode 100644 icon.png create mode 100644 resources/language/english/strings.xml create mode 100644 resources/lib/brlog.py create mode 100644 resources/lib/elementtree/ElementPath.py create mode 100644 resources/lib/elementtree/ElementTree.py create mode 100644 resources/lib/elementtree/__init__.py create mode 100644 resources/lib/file.py create mode 100644 resources/lib/makemkv.py create mode 100644 resources/lib/mkvparser.py create mode 100644 resources/lib/settings.py create mode 100644 resources/settings.xml create mode 100644 service.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..34fcef1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyo +*.pyc diff --git a/addon.xml b/addon.xml new file mode 100644 index 0000000..722523e --- /dev/null +++ b/addon.xml @@ -0,0 +1,18 @@ + + + + + + + video + + + + + Play BluRays from XBMC with MakeMKV + Awesomeness + all + + diff --git a/default.py b/default.py new file mode 100644 index 0000000..b49fcba --- /dev/null +++ b/default.py @@ -0,0 +1,200 @@ +import xbmc, xbmcgui, subprocess, os, time, sys, urllib, re +import xbmcplugin, xbmcaddon + +# Shared resources +BASE_RESOURCE_PATH = os.path.join( xbmcaddon.Addon().getAddonInfo('path'), "resources" ) +sys.path.append( os.path.join( BASE_RESOURCE_PATH, "lib" ) ) + + +__scriptname__ = "MakeMKV BluRay Watch Plugin" +__scriptID__ = "plugin.makemkvbluray" +__author__ = "Magnetism" +__url__ = "http://bultsblog.com/arne" +__credits__ = "" +__version__ = "0.1" +__addon__ = xbmcaddon.Addon(__scriptID__) + +__language__ = __addon__.getLocalizedString +_ = sys.modules[ "__main__" ].__language__ + +import settings, file, mkvparser, brlog, makemkv + +_log = brlog.BrLog() + +_log.info('Starting the BluRay script') #@UndefinedVariable + +class BluRayStarter: + def __init__(self): + _log.info('Staring') #@UndefinedVariable + self.settings = settings.BluRaySettings() + self.makemkv = makemkv.MakeMkvInteraction() + + def killAndStart(self, mkvStart): + if self.settings.local: + _log.info('Running makemkvcon locally') #@UndefinedVariable + self.killMkv() + # Determine if we're doing the disc or if we're browsing.. + _log.info(mkvStart) #@UndefinedVariable + return subprocess.Popen(mkvStart, shell=True) + else: + _log.info('connecting to remote stream, returning fake file browse class..') #@UndefinedVariable + return file.FakeFile() + + def killMkv(self): + # Linux + try : + _log.info('attempting linux kill of makemkvcon') #@UndefinedVariable + subprocess.call('killall -9 makemkvcon', shell=True) + _log.info('Linux call successful') #@UndefinedVariable + except: + pass + + #Windows. + try : + _log.info('attempting windows kill of makemkvcon') #@UndefinedVariable + subprocess.call('taskkill /F /IM makemkvcon.exe', shell=True) + _log.info('Windows call successful') #@UndefinedVariable + except: + pass + + def browse(self, url) : + _log.info('starting browser handler') #@UndefinedVariable + h = mkvparser.BrowseHandler() + h.start(url) + for k,v in h.titleMap.iteritems() : #@UnusedVariable + self.addLink("%s %s, %s %s" %(_(50005), v['duration'], _(50006), v['chaptercount']),v['file']) + + + def getMainFeatureTitle(self, url): + h = mkvparser.BrowseHandler() + h.start(url) + # Play the longest feature on the disc: + largest = 0 + largestTitle = '' + for k,v in h.titleMap.iteritems() : #@UnusedVariable + m = re.search('(\d+):(\d+):(\d+)', v['duration']) + length = int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3)) + if length > largest : + largest = length + largestTitle = v['file'] + _log.info('largest: %d, %s' %(largest,largestTitle)) + return largestTitle + + def handleListing(self): + mode = self.settings.paramMode + _log.info( 'mode: ' + str(mode)) + if mode ==None: + _log.info('Showing categories') + self.CATEGORIES() + _log.info('Showing categories done') +# _log.info(__addon__.) + xbmcplugin.endOfDirectory(int(sys.argv[1])) + + if mode == 1 : + _log.info( 'Entering Disc mode') + self.process(self.makemkv.startStream(self.settings.disc)) + elif mode == 3 : + _log.info( 'Entering Remote mode') + mystarter = BluRayStarter() + mystarter.process('') + elif mode == 2: + _log.info( 'Entering Browse mode') + d = xbmcgui.Dialog() #@UndefinedVariable + choice = d.browse(1, 'Select folder', 'video', 'index.bdmv|.iso|.isoRenamedMeansSkip!|.MDS|.CUE|.CDI|.CCD', False, False, '') + if choice <> '': + self.process(self.makemkv.startFileStream(choice)) + + if mode == 20: + self.settings.showSettings() + + def process(self, ready): + try : + if ready: + _log.info( 'Stream ready. ') + # the Stream has started, start auto playback? + if self.settings.autoPlay: + _log.info( 'Autoplay selected') + title = self.getMainFeatureTitle(self.settings.rootURL) + _log.info( 'Main feature determined to be : ' + title) + opener = urllib.URLopener() + testfile = '' + try: + testfile = title + opener.open(testfile) + except IOError: + testfile = '' + + del opener + + if testfile<>'': + _log.info( 'Playing file ' + testfile) + li = xbmcgui.ListItem(path = testfile) + li.setProperty('IsPlayable', 'true') + xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, li) #@UndefinedVariable + else: + self.message(_(50071)) + xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, xbmcgui.ListItem()) #@UndefinedVariable + + else: + # Add the selections as selectable files. + self.browse(self.settings.rootURL) + xbmcplugin.endOfDirectory(int(sys.argv[1])) #@UndefinedVariable + + + except : + self.message(_(50072)) + self.pDialog.close() + xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, xbmcgui.ListItem()) #@UndefinedVariable + raise + + def CATEGORIES(self): + # Disc + if self.settings.enableDisc: + disclist = self.makemkv.discList() + for disc in disclist: + self.addDir(_(50061) %(disc[0], disc[1]),1, True, disc[0]) + for disc in disclist: + self.addDir(_(50062) %(disc[0], disc[1]),1, False, disc[0]) + # Filelocation + if self.settings.enableFile: + self.addDir(_(50063),2, True) + self.addDir(_(50064),2, False) + # Remote + # if self.settings.enableRemote: + # self.addDir(_(50065),3, True) + # self.addDir(_(50066),3, False) + self.addDir(_(50060),20, True, '0', False) + xbmcplugin.endOfDirectory(int(sys.argv[1])) + + + def addDir(self, name,mode, autoplay, disc = '0', isPlayable = True): + u=sys.argv[0]+"?mode="+str(mode)+"&autoplay="+urllib.quote_plus(str(autoplay)) + "&disc=" + disc + _log.info(u) + icon = "DefaultVideoPlaylists.png" + if autoplay: + icon= "DefaultVideo.png" + liz=xbmcgui.ListItem(name, iconImage=icon, thumbnailImage='') + if autoplay and isPlayable: + liz.setProperty("IsPlayable", "true") + liz.setInfo( type="Video", infoLabels={ "Title": name } ) + _log.info(name) + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz, isFolder= not autoplay) + + + def addLink(self, name,url): + liz=xbmcgui.ListItem(name, iconImage="DefaultVideo.png", thumbnailImage='') #@UndefinedVariable + liz.setInfo( type="Video", infoLabels={ "Title": name } ) + liz.setProperty("IsPlayable" , "true") + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz) #@UndefinedVariable + + def message(self, messageText): + dialog = xbmcgui.Dialog() #@UndefinedVariable + dialog.ok("Info", messageText) + +_log.info("args") +for arg in sys.argv: + _log.info(arg) +_log.info("done args") + +mydisplay = BluRayStarter() +mydisplay.handleListing() diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d2ebaf0c98dc6d627a9ec7b5cbc2c552cbd006ef GIT binary patch literal 12816 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 + MakeMKV location + Autoplay main feature + Remote Setup + Remote IP address + Local Port number + Seconds to wait for stream + General Settings + Browseable Options + Enable Disc support + Enable File location support + Enable Remote location support + Remote Port number + + duration + chapters + + Starting BluRay script + Initializing + Waiting for BluRay to be prepared + Starting Disc + Starting Image + Starting Directory + Waiting for stream + + + Settings + + Play Disc %s: %s + Browse Disc %s: %s + Play Filelocation + Browse Filelocation + Play Remote location + Browse Remote location + + + Running MakeMKV ended abnormally. Is it installed? + unable to find autoplay stream + Error trying to open makemkv stream + The file you've selected cannot be accessed by the filesystem + diff --git a/resources/lib/brlog.py b/resources/lib/brlog.py new file mode 100644 index 0000000..e4ba1b1 --- /dev/null +++ b/resources/lib/brlog.py @@ -0,0 +1,32 @@ + +class BrLog: + __DEBUG = 0 + __INFO = 1 + __WARN = 2 + __ERROR = 3 + + def __init__(self, prefix = ''): + self.logLevel = self.__INFO + if prefix <> '': + prefix = '-' + prefix + self.prefix = prefix + + def setLevel(self, level): + if level >= 0 and level <= 3: + self.logLevel = level + + def info(self, message): + self.log(message, self.__INFO) + + def debug(self, message): + self.log(message, self.__DEBUG) + + def error(self, message): + self.log(message, self.__ERROR) + + def warn(self, message): + self.log(message, self.__WARN) + + def log(self, message, level): + if self.logLevel <= level: + print '[BR%s %d] %s' %(self.prefix, level, message) diff --git a/resources/lib/elementtree/ElementPath.py b/resources/lib/elementtree/ElementPath.py new file mode 100644 index 0000000..e8093c6 --- /dev/null +++ b/resources/lib/elementtree/ElementPath.py @@ -0,0 +1,196 @@ +# +# ElementTree +# $Id: ElementPath.py 1858 2004-06-17 21:31:41Z Fredrik $ +# +# limited xpath support for element trees +# +# history: +# 2003-05-23 fl created +# 2003-05-28 fl added support for // etc +# 2003-08-27 fl fixed parsing of periods in element names +# +# Copyright (c) 2003-2004 by Fredrik Lundh. All rights reserved. +# +# fredrik@pythonware.com +# http://www.pythonware.com +# +# -------------------------------------------------------------------- +# The ElementTree toolkit is +# +# Copyright (c) 1999-2004 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +## +# Implementation module for XPath support. There's usually no reason +# to import this module directly; the ElementTree does this for +# you, if needed. +## + +import re + +xpath_tokenizer = re.compile( + "(::|\.\.|\(\)|[/.*:\[\]\(\)@=])|((?:\{[^}]+\})?[^/:\[\]\(\)@=\s]+)|\s+" + ).findall + +class xpath_descendant_or_self: + pass + +## +# Wrapper for a compiled XPath. + +class Path: + + ## + # Create an Path instance from an XPath expression. + + def __init__(self, path): + tokens = xpath_tokenizer(path) + # the current version supports 'path/path'-style expressions only + self.path = [] + self.tag = None + if tokens and tokens[0][0] == "/": + raise SyntaxError("cannot use absolute path on element") + while tokens: + op, tag = tokens.pop(0) + if tag or op == "*": + self.path.append(tag or op) + elif op == ".": + pass + elif op == "/": + self.path.append(xpath_descendant_or_self()) + continue + else: + raise SyntaxError("unsupported path syntax (%s)" % op) + if tokens: + op, tag = tokens.pop(0) + if op != "/": + raise SyntaxError( + "expected path separator (%s)" % (op or tag) + ) + if self.path and isinstance(self.path[-1], xpath_descendant_or_self): + raise SyntaxError("path cannot end with //") + if len(self.path) == 1 and isinstance(self.path[0], type("")): + self.tag = self.path[0] + + ## + # Find first matching object. + + def find(self, element): + tag = self.tag + if tag is None: + nodeset = self.findall(element) + if not nodeset: + return None + return nodeset[0] + for elem in element: + if elem.tag == tag: + return elem + return None + + ## + # Find text for first matching object. + + def findtext(self, element, default=None): + tag = self.tag + if tag is None: + nodeset = self.findall(element) + if not nodeset: + return default + return nodeset[0].text or "" + for elem in element: + if elem.tag == tag: + return elem.text or "" + return default + + ## + # Find all matching objects. + + def findall(self, element): + nodeset = [element] + index = 0 + while 1: + try: + path = self.path[index] + index = index + 1 + except IndexError: + return nodeset + set = [] + if isinstance(path, xpath_descendant_or_self): + try: + tag = self.path[index] + if not isinstance(tag, type("")): + tag = None + else: + index = index + 1 + except IndexError: + tag = None # invalid path + for node in nodeset: + new = list(node.getiterator(tag)) + if new and new[0] is node: + set.extend(new[1:]) + else: + set.extend(new) + else: + for node in nodeset: + for node in node: + if path == "*" or node.tag == path: + set.append(node) + if not set: + return [] + nodeset = set + +_cache = {} + +## +# (Internal) Compile path. + +def _compile(path): + p = _cache.get(path) + if p is not None: + return p + p = Path(path) + if len(_cache) >= 100: + _cache.clear() + _cache[path] = p + return p + +## +# Find first matching object. + +def find(element, path): + return _compile(path).find(element) + +## +# Find text for first matching object. + +def findtext(element, path, default=None): + return _compile(path).findtext(element, default) + +## +# Find all matching objects. + +def findall(element, path): + return _compile(path).findall(element) + diff --git a/resources/lib/elementtree/ElementTree.py b/resources/lib/elementtree/ElementTree.py new file mode 100644 index 0000000..bd45251 --- /dev/null +++ b/resources/lib/elementtree/ElementTree.py @@ -0,0 +1,1254 @@ +# +# ElementTree +# $Id: ElementTree.py 2326 2005-03-17 07:45:21Z fredrik $ +# +# light-weight XML support for Python 1.5.2 and later. +# +# history: +# 2001-10-20 fl created (from various sources) +# 2001-11-01 fl return root from parse method +# 2002-02-16 fl sort attributes in lexical order +# 2002-04-06 fl TreeBuilder refactoring, added PythonDoc markup +# 2002-05-01 fl finished TreeBuilder refactoring +# 2002-07-14 fl added basic namespace support to ElementTree.write +# 2002-07-25 fl added QName attribute support +# 2002-10-20 fl fixed encoding in write +# 2002-11-24 fl changed default encoding to ascii; fixed attribute encoding +# 2002-11-27 fl accept file objects or file names for parse/write +# 2002-12-04 fl moved XMLTreeBuilder back to this module +# 2003-01-11 fl fixed entity encoding glitch for us-ascii +# 2003-02-13 fl added XML literal factory +# 2003-02-21 fl added ProcessingInstruction/PI factory +# 2003-05-11 fl added tostring/fromstring helpers +# 2003-05-26 fl added ElementPath support +# 2003-07-05 fl added makeelement factory method +# 2003-07-28 fl added more well-known namespace prefixes +# 2003-08-15 fl fixed typo in ElementTree.findtext (Thomas Dartsch) +# 2003-09-04 fl fall back on emulator if ElementPath is not installed +# 2003-10-31 fl markup updates +# 2003-11-15 fl fixed nested namespace bug +# 2004-03-28 fl added XMLID helper +# 2004-06-02 fl added default support to findtext +# 2004-06-08 fl fixed encoding of non-ascii element/attribute names +# 2004-08-23 fl take advantage of post-2.1 expat features +# 2005-02-01 fl added iterparse implementation +# 2005-03-02 fl fixed iterparse support for pre-2.2 versions +# +# Copyright (c) 1999-2005 by Fredrik Lundh. All rights reserved. +# +# fredrik@pythonware.com +# http://www.pythonware.com +# +# -------------------------------------------------------------------- +# The ElementTree toolkit is +# +# Copyright (c) 1999-2005 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +__all__ = [ + # public symbols + "Comment", + "dump", + "Element", "ElementTree", + "fromstring", + "iselement", "iterparse", + "parse", + "PI", "ProcessingInstruction", + "QName", + "SubElement", + "tostring", + "TreeBuilder", + "VERSION", "XML", + "XMLTreeBuilder", + ] + +## +# The Element type is a flexible container object, designed to +# store hierarchical data structures in memory. The type can be +# described as a cross between a list and a dictionary. +#

+# Each element has a number of properties associated with it: +#

    +#
  • a tag. This is a string identifying what kind of data +# this element represents (the element type, in other words).
  • +#
  • a number of attributes, stored in a Python dictionary.
  • +#
  • a text string.
  • +#
  • an optional tail string.
  • +#
  • a number of child elements, stored in a Python sequence
  • +#
+# +# To create an element instance, use the {@link #Element} or {@link +# #SubElement} factory functions. +#

+# The {@link #ElementTree} class can be used to wrap an element +# structure, and convert it from and to XML. +## + +import string, sys, re + +class _SimpleElementPath: + # emulate pre-1.2 find/findtext/findall behaviour + def find(self, element, tag): + for elem in element: + if elem.tag == tag: + return elem + return None + def findtext(self, element, tag, default=None): + for elem in element: + if elem.tag == tag: + return elem.text or "" + return default + def findall(self, element, tag): + if tag[:3] == ".//": + return element.getiterator(tag[3:]) + result = [] + for elem in element: + if elem.tag == tag: + result.append(elem) + return result + +try: + import ElementPath +except ImportError: + # FIXME: issue warning in this case? + ElementPath = _SimpleElementPath() + +# TODO: add support for custom namespace resolvers/default namespaces +# TODO: add improved support for incremental parsing + +VERSION = "1.2.6" + +## +# Internal element class. This class defines the Element interface, +# and provides a reference implementation of this interface. +#

+# You should not create instances of this class directly. Use the +# appropriate factory functions instead, such as {@link #Element} +# and {@link #SubElement}. +# +# @see Element +# @see SubElement +# @see Comment +# @see ProcessingInstruction + +class _ElementInterface: + # text...tail + + ## + # (Attribute) Element tag. + + tag = None + + ## + # (Attribute) Element attribute dictionary. Where possible, use + # {@link #_ElementInterface.get}, + # {@link #_ElementInterface.set}, + # {@link #_ElementInterface.keys}, and + # {@link #_ElementInterface.items} to access + # element attributes. + + attrib = None + + ## + # (Attribute) Text before first subelement. This is either a + # string or the value None, if there was no text. + + text = None + + ## + # (Attribute) Text after this element's end tag, but before the + # next sibling element's start tag. This is either a string or + # the value None, if there was no text. + + tail = None # text after end tag, if any + + def __init__(self, tag, attrib): + self.tag = tag + self.attrib = attrib + self._children = [] + + def __repr__(self): + return "" % (self.tag, id(self)) + + ## + # Creates a new element object of the same type as this element. + # + # @param tag Element tag. + # @param attrib Element attributes, given as a dictionary. + # @return A new element instance. + + def makeelement(self, tag, attrib): + return Element(tag, attrib) + + ## + # Returns the number of subelements. + # + # @return The number of subelements. + + def __len__(self): + return len(self._children) + + ## + # Returns the given subelement. + # + # @param index What subelement to return. + # @return The given subelement. + # @exception IndexError If the given element does not exist. + + def __getitem__(self, index): + return self._children[index] + + ## + # Replaces the given subelement. + # + # @param index What subelement to replace. + # @param element The new element value. + # @exception IndexError If the given element does not exist. + # @exception AssertionError If element is not a valid object. + + def __setitem__(self, index, element): + assert iselement(element) + self._children[index] = element + + ## + # Deletes the given subelement. + # + # @param index What subelement to delete. + # @exception IndexError If the given element does not exist. + + def __delitem__(self, index): + del self._children[index] + + ## + # Returns a list containing subelements in the given range. + # + # @param start The first subelement to return. + # @param stop The first subelement that shouldn't be returned. + # @return A sequence object containing subelements. + + def __getslice__(self, start, stop): + return self._children[start:stop] + + ## + # Replaces a number of subelements with elements from a sequence. + # + # @param start The first subelement to replace. + # @param stop The first subelement that shouldn't be replaced. + # @param elements A sequence object with zero or more elements. + # @exception AssertionError If a sequence member is not a valid object. + + def __setslice__(self, start, stop, elements): + for element in elements: + assert iselement(element) + self._children[start:stop] = list(elements) + + ## + # Deletes a number of subelements. + # + # @param start The first subelement to delete. + # @param stop The first subelement to leave in there. + + def __delslice__(self, start, stop): + del self._children[start:stop] + + ## + # Adds a subelement to the end of this element. + # + # @param element The element to add. + # @exception AssertionError If a sequence member is not a valid object. + + def append(self, element): + assert iselement(element) + self._children.append(element) + + ## + # Inserts a subelement at the given position in this element. + # + # @param index Where to insert the new subelement. + # @exception AssertionError If the element is not a valid object. + + def insert(self, index, element): + assert iselement(element) + self._children.insert(index, element) + + ## + # Removes a matching subelement. Unlike the find methods, + # this method compares elements based on identity, not on tag + # value or contents. + # + # @param element What element to remove. + # @exception ValueError If a matching element could not be found. + # @exception AssertionError If the element is not a valid object. + + def remove(self, element): + assert iselement(element) + self._children.remove(element) + + ## + # Returns all subelements. The elements are returned in document + # order. + # + # @return A list of subelements. + # @defreturn list of Element instances + + def getchildren(self): + return self._children + + ## + # Finds the first matching subelement, by tag name or path. + # + # @param path What element to look for. + # @return The first matching element, or None if no element was found. + # @defreturn Element or None + + def find(self, path): + return ElementPath.find(self, path) + + ## + # Finds text for the first matching subelement, by tag name or path. + # + # @param path What element to look for. + # @param default What to return if the element was not found. + # @return The text content of the first matching element, or the + # default value no element was found. Note that if the element + # has is found, but has no text content, this method returns an + # empty string. + # @defreturn string + + def findtext(self, path, default=None): + return ElementPath.findtext(self, path, default) + + ## + # Finds all matching subelements, by tag name or path. + # + # @param path What element to look for. + # @return A list or iterator containing all matching elements, + # in document order. + # @defreturn list of Element instances + + def findall(self, path): + return ElementPath.findall(self, path) + + ## + # Resets an element. This function removes all subelements, clears + # all attributes, and sets the text and tail attributes to None. + + def clear(self): + self.attrib.clear() + self._children = [] + self.text = self.tail = None + + ## + # Gets an element attribute. + # + # @param key What attribute to look for. + # @param default What to return if the attribute was not found. + # @return The attribute value, or the default value, if the + # attribute was not found. + # @defreturn string or None + + def get(self, key, default=None): + return self.attrib.get(key, default) + + ## + # Sets an element attribute. + # + # @param key What attribute to set. + # @param value The attribute value. + + def set(self, key, value): + self.attrib[key] = value + + ## + # Gets a list of attribute names. The names are returned in an + # arbitrary order (just like for an ordinary Python dictionary). + # + # @return A list of element attribute names. + # @defreturn list of strings + + def keys(self): + return self.attrib.keys() + + ## + # Gets element attributes, as a sequence. The attributes are + # returned in an arbitrary order. + # + # @return A list of (name, value) tuples for all attributes. + # @defreturn list of (string, string) tuples + + def items(self): + return self.attrib.items() + + ## + # Creates a tree iterator. The iterator loops over this element + # and all subelements, in document order, and returns all elements + # with a matching tag. + #

+ # If the tree structure is modified during iteration, the result + # is undefined. + # + # @param tag What tags to look for (default is to return all elements). + # @return A list or iterator containing all the matching elements. + # @defreturn list or iterator + + def getiterator(self, tag=None): + nodes = [] + if tag == "*": + tag = None + if tag is None or self.tag == tag: + nodes.append(self) + for node in self._children: + nodes.extend(node.getiterator(tag)) + return nodes + +# compatibility +_Element = _ElementInterface + +## +# Element factory. This function returns an object implementing the +# standard Element interface. The exact class or type of that object +# is implementation dependent, but it will always be compatible with +# the {@link #_ElementInterface} class in this module. +#

+# The element name, attribute names, and attribute values can be +# either 8-bit ASCII strings or Unicode strings. +# +# @param tag The element name. +# @param attrib An optional dictionary, containing element attributes. +# @param **extra Additional attributes, given as keyword arguments. +# @return An element instance. +# @defreturn Element + +def Element(tag, attrib={}, **extra): + attrib = attrib.copy() + attrib.update(extra) + return _ElementInterface(tag, attrib) + +## +# Subelement factory. This function creates an element instance, and +# appends it to an existing element. +#

+# The element name, attribute names, and attribute values can be +# either 8-bit ASCII strings or Unicode strings. +# +# @param parent The parent element. +# @param tag The subelement name. +# @param attrib An optional dictionary, containing element attributes. +# @param **extra Additional attributes, given as keyword arguments. +# @return An element instance. +# @defreturn Element + +def SubElement(parent, tag, attrib={}, **extra): + attrib = attrib.copy() + attrib.update(extra) + element = parent.makeelement(tag, attrib) + parent.append(element) + return element + +## +# Comment element factory. This factory function creates a special +# element that will be serialized as an XML comment. +#

+# The comment string can be either an 8-bit ASCII string or a Unicode +# string. +# +# @param text A string containing the comment string. +# @return An element instance, representing a comment. +# @defreturn Element + +def Comment(text=None): + element = Element(Comment) + element.text = text + return element + +## +# PI element factory. This factory function creates a special element +# that will be serialized as an XML processing instruction. +# +# @param target A string containing the PI target. +# @param text A string containing the PI contents, if any. +# @return An element instance, representing a PI. +# @defreturn Element + +def ProcessingInstruction(target, text=None): + element = Element(ProcessingInstruction) + element.text = target + if text: + element.text = element.text + " " + text + return element + +PI = ProcessingInstruction + +## +# QName wrapper. This can be used to wrap a QName attribute value, in +# order to get proper namespace handling on output. +# +# @param text A string containing the QName value, in the form {uri}local, +# or, if the tag argument is given, the URI part of a QName. +# @param tag Optional tag. If given, the first argument is interpreted as +# an URI, and this argument is interpreted as a local name. +# @return An opaque object, representing the QName. + +class QName: + def __init__(self, text_or_uri, tag=None): + if tag: + text_or_uri = "{%s}%s" % (text_or_uri, tag) + self.text = text_or_uri + def __str__(self): + return self.text + def __hash__(self): + return hash(self.text) + def __cmp__(self, other): + if isinstance(other, QName): + return cmp(self.text, other.text) + return cmp(self.text, other) + +## +# ElementTree wrapper class. This class represents an entire element +# hierarchy, and adds some extra support for serialization to and from +# standard XML. +# +# @param element Optional root element. +# @keyparam file Optional file handle or name. If given, the +# tree is initialized with the contents of this XML file. + +class ElementTree: + + def __init__(self, element=None, file=None): + assert element is None or iselement(element) + self._root = element # first node + if file: + self.parse(file) + + ## + # Gets the root element for this tree. + # + # @return An element instance. + # @defreturn Element + + def getroot(self): + return self._root + + ## + # Replaces the root element for this tree. This discards the + # current contents of the tree, and replaces it with the given + # element. Use with care. + # + # @param element An element instance. + + def _setroot(self, element): + assert iselement(element) + self._root = element + + ## + # Loads an external XML document into this element tree. + # + # @param source A file name or file object. + # @param parser An optional parser instance. If not given, the + # standard {@link XMLTreeBuilder} parser is used. + # @return The document root element. + # @defreturn Element + + def parse(self, source, parser=None): + if not hasattr(source, "read"): + source = open(source, "rb") + if not parser: + parser = XMLTreeBuilder() + while 1: + data = source.read(32768) + if not data: + break + parser.feed(data) + self._root = parser.close() + return self._root + + ## + # Creates a tree iterator for the root element. The iterator loops + # over all elements in this tree, in document order. + # + # @param tag What tags to look for (default is to return all elements) + # @return An iterator. + # @defreturn iterator + + def getiterator(self, tag=None): + assert self._root is not None + return self._root.getiterator(tag) + + ## + # Finds the first toplevel element with given tag. + # Same as getroot().find(path). + # + # @param path What element to look for. + # @return The first matching element, or None if no element was found. + # @defreturn Element or None + + def find(self, path): + assert self._root is not None + if path[:1] == "/": + path = "." + path + return self._root.find(path) + + ## + # Finds the element text for the first toplevel element with given + # tag. Same as getroot().findtext(path). + # + # @param path What toplevel element to look for. + # @param default What to return if the element was not found. + # @return The text content of the first matching element, or the + # default value no element was found. Note that if the element + # has is found, but has no text content, this method returns an + # empty string. + # @defreturn string + + def findtext(self, path, default=None): + assert self._root is not None + if path[:1] == "/": + path = "." + path + return self._root.findtext(path, default) + + ## + # Finds all toplevel elements with the given tag. + # Same as getroot().findall(path). + # + # @param path What element to look for. + # @return A list or iterator containing all matching elements, + # in document order. + # @defreturn list of Element instances + + def findall(self, path): + assert self._root is not None + if path[:1] == "/": + path = "." + path + return self._root.findall(path) + + ## + # Writes the element tree to a file, as XML. + # + # @param file A file name, or a file object opened for writing. + # @param encoding Optional output encoding (default is US-ASCII). + + def write(self, file, encoding="us-ascii"): + assert self._root is not None + if not hasattr(file, "write"): + file = open(file, "wb") + if not encoding: + encoding = "us-ascii" + elif encoding != "utf-8" and encoding != "us-ascii": + file.write("\n" % encoding) + self._write(file, self._root, encoding, {}) + + def _write(self, file, node, encoding, namespaces): + # write XML to file + tag = node.tag + if tag is Comment: + file.write("" % _escape_cdata(node.text, encoding)) + elif tag is ProcessingInstruction: + file.write("" % _escape_cdata(node.text, encoding)) + else: + items = node.items() + xmlns_items = [] # new namespaces in this scope + try: + if isinstance(tag, QName) or tag[:1] == "{": + tag, xmlns = fixtag(tag, namespaces) + if xmlns: xmlns_items.append(xmlns) + except TypeError: + _raise_serialization_error(tag) + file.write("<" + _encode(tag, encoding)) + if items or xmlns_items: + items.sort() # lexical order + for k, v in items: + try: + if isinstance(k, QName) or k[:1] == "{": + k, xmlns = fixtag(k, namespaces) + if xmlns: xmlns_items.append(xmlns) + except TypeError: + _raise_serialization_error(k) + try: + if isinstance(v, QName): + v, xmlns = fixtag(v, namespaces) + if xmlns: xmlns_items.append(xmlns) + except TypeError: + _raise_serialization_error(v) + file.write(" %s=\"%s\"" % (_encode(k, encoding), + _escape_attrib(v, encoding))) + for k, v in xmlns_items: + file.write(" %s=\"%s\"" % (_encode(k, encoding), + _escape_attrib(v, encoding))) + if node.text or len(node): + file.write(">") + if node.text: + file.write(_escape_cdata(node.text, encoding)) + for n in node: + self._write(file, n, encoding, namespaces) + file.write("") + else: + file.write(" />") + for k, v in xmlns_items: + del namespaces[v] + if node.tail: + file.write(_escape_cdata(node.tail, encoding)) + +# -------------------------------------------------------------------- +# helpers + +## +# Checks if an object appears to be a valid element object. +# +# @param An element instance. +# @return A true value if this is an element object. +# @defreturn flag + +def iselement(element): + # FIXME: not sure about this; might be a better idea to look + # for tag/attrib/text attributes + return isinstance(element, _ElementInterface) or hasattr(element, "tag") + +## +# Writes an element tree or element structure to sys.stdout. This +# function should be used for debugging only. +#

+# The exact output format is implementation dependent. In this +# version, it's written as an ordinary XML file. +# +# @param elem An element tree or an individual element. + +def dump(elem): + # debugging + if not isinstance(elem, ElementTree): + elem = ElementTree(elem) + elem.write(sys.stdout) + tail = elem.getroot().tail + if not tail or tail[-1] != "\n": + sys.stdout.write("\n") + +def _encode(s, encoding): + try: + return s.encode(encoding) + except AttributeError: + return s # 1.5.2: assume the string uses the right encoding + +if sys.version[:3] == "1.5": + _escape = re.compile(r"[&<>\"\x80-\xff]+") # 1.5.2 +else: + _escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]+"')) + +_escape_map = { + "&": "&", + "<": "<", + ">": ">", + '"': """, +} + +_namespace_map = { + # "well-known" namespace prefixes + "http://www.w3.org/XML/1998/namespace": "xml", + "http://www.w3.org/1999/xhtml": "html", + "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf", + "http://schemas.xmlsoap.org/wsdl/": "wsdl", +} + +def _raise_serialization_error(text): + raise TypeError( + "cannot serialize %r (type %s)" % (text, type(text).__name__) + ) + +def _encode_entity(text, pattern=_escape): + # map reserved and non-ascii characters to numerical entities + def escape_entities(m, map=_escape_map): + out = [] + append = out.append + for char in m.group(): + text = map.get(char) + if text is None: + text = "&#%d;" % ord(char) + append(text) + return string.join(out, "") + try: + return _encode(pattern.sub(escape_entities, text), "ascii") + except TypeError: + _raise_serialization_error(text) + +# +# the following functions assume an ascii-compatible encoding +# (or "utf-16") + +def _escape_cdata(text, encoding=None, replace=string.replace): + # escape character data + try: + if encoding: + try: + text = _encode(text, encoding) + except UnicodeError: + return _encode_entity(text) + text = replace(text, "&", "&") + text = replace(text, "<", "<") + text = replace(text, ">", ">") + return text + except (TypeError, AttributeError): + _raise_serialization_error(text) + +def _escape_attrib(text, encoding=None, replace=string.replace): + # escape attribute value + try: + if encoding: + try: + text = _encode(text, encoding) + except UnicodeError: + return _encode_entity(text) + text = replace(text, "&", "&") + text = replace(text, "'", "'") # FIXME: overkill + text = replace(text, "\"", """) + text = replace(text, "<", "<") + text = replace(text, ">", ">") + return text + except (TypeError, AttributeError): + _raise_serialization_error(text) + +def fixtag(tag, namespaces): + # given a decorated tag (of the form {uri}tag), return prefixed + # tag and namespace declaration, if any + if isinstance(tag, QName): + tag = tag.text + namespace_uri, tag = string.split(tag[1:], "}", 1) + prefix = namespaces.get(namespace_uri) + if prefix is None: + prefix = _namespace_map.get(namespace_uri) + if prefix is None: + prefix = "ns%d" % len(namespaces) + namespaces[namespace_uri] = prefix + if prefix == "xml": + xmlns = None + else: + xmlns = ("xmlns:%s" % prefix, namespace_uri) + else: + xmlns = None + return "%s:%s" % (prefix, tag), xmlns + +## +# Parses an XML document into an element tree. +# +# @param source A filename or file object containing XML data. +# @param parser An optional parser instance. If not given, the +# standard {@link XMLTreeBuilder} parser is used. +# @return An ElementTree instance + +def parse(source, parser=None): + tree = ElementTree() + tree.parse(source, parser) + return tree + +## +# Parses an XML document into an element tree incrementally, and reports +# what's going on to the user. +# +# @param source A filename or file object containing XML data. +# @param events A list of events to report back. If omitted, only "end" +# events are reported. +# @return A (event, elem) iterator. + +class iterparse: + + def __init__(self, source, events=None): + if not hasattr(source, "read"): + source = open(source, "rb") + self._file = source + self._events = [] + self._index = 0 + self.root = self._root = None + self._parser = XMLTreeBuilder() + # wire up the parser for event reporting + parser = self._parser._parser + append = self._events.append + if events is None: + events = ["end"] + for event in events: + if event == "start": + try: + parser.ordered_attributes = 1 + parser.specified_attributes = 1 + def handler(tag, attrib_in, event=event, append=append, + start=self._parser._start_list): + append((event, start(tag, attrib_in))) + parser.StartElementHandler = handler + except AttributeError: + def handler(tag, attrib_in, event=event, append=append, + start=self._parser._start): + append((event, start(tag, attrib_in))) + parser.StartElementHandler = handler + elif event == "end": + def handler(tag, event=event, append=append, + end=self._parser._end): + append((event, end(tag))) + parser.EndElementHandler = handler + elif event == "start-ns": + def handler(prefix, uri, event=event, append=append): + try: + uri = _encode(uri, "ascii") + except UnicodeError: + pass + append((event, (prefix or "", uri))) + parser.StartNamespaceDeclHandler = handler + elif event == "end-ns": + def handler(prefix, event=event, append=append): + append((event, None)) + parser.EndNamespaceDeclHandler = handler + + def next(self): + while 1: + try: + item = self._events[self._index] + except IndexError: + if self._parser is None: + self.root = self._root + try: + raise StopIteration + except NameError: + raise IndexError + # load event buffer + del self._events[:] + self._index = 0 + data = self._file.read(16384) + if data: + self._parser.feed(data) + else: + self._root = self._parser.close() + self._parser = None + else: + self._index = self._index + 1 + return item + + try: + iter + def __iter__(self): + return self + except NameError: + def __getitem__(self, index): + return self.next() + +## +# Parses an XML document from a string constant. This function can +# be used to embed "XML literals" in Python code. +# +# @param source A string containing XML data. +# @return An Element instance. +# @defreturn Element + +def XML(text): + parser = XMLTreeBuilder() + parser.feed(text) + return parser.close() + +## +# Parses an XML document from a string constant, and also returns +# a dictionary which maps from element id:s to elements. +# +# @param source A string containing XML data. +# @return A tuple containing an Element instance and a dictionary. +# @defreturn (Element, dictionary) + +def XMLID(text): + parser = XMLTreeBuilder() + parser.feed(text) + tree = parser.close() + ids = {} + for elem in tree.getiterator(): + id = elem.get("id") + if id: + ids[id] = elem + return tree, ids + +## +# Parses an XML document from a string constant. Same as {@link #XML}. +# +# @def fromstring(text) +# @param source A string containing XML data. +# @return An Element instance. +# @defreturn Element + +fromstring = XML + +## +# Generates a string representation of an XML element, including all +# subelements. +# +# @param element An Element instance. +# @return An encoded string containing the XML data. +# @defreturn string + +def tostring(element, encoding=None): + class dummy: + pass + data = [] + file = dummy() + file.write = data.append + ElementTree(element).write(file, encoding) + return string.join(data, "") + +## +# Generic element structure builder. This builder converts a sequence +# of {@link #TreeBuilder.start}, {@link #TreeBuilder.data}, and {@link +# #TreeBuilder.end} method calls to a well-formed element structure. +#

+# You can use this class to build an element structure using a custom XML +# parser, or a parser for some other XML-like format. +# +# @param element_factory Optional element factory. This factory +# is called to create new Element instances, as necessary. + +class TreeBuilder: + + def __init__(self, element_factory=None): + self._data = [] # data collector + self._elem = [] # element stack + self._last = None # last element + self._tail = None # true if we're after an end tag + if element_factory is None: + element_factory = _ElementInterface + self._factory = element_factory + + ## + # Flushes the parser buffers, and returns the toplevel documen + # element. + # + # @return An Element instance. + # @defreturn Element + + def close(self): + assert len(self._elem) == 0, "missing end tags" + assert self._last != None, "missing toplevel element" + return self._last + + def _flush(self): + if self._data: + if self._last is not None: + text = string.join(self._data, "") + if self._tail: + assert self._last.tail is None, "internal error (tail)" + self._last.tail = text + else: + assert self._last.text is None, "internal error (text)" + self._last.text = text + self._data = [] + + ## + # Adds text to the current element. + # + # @param data A string. This should be either an 8-bit string + # containing ASCII text, or a Unicode string. + + def data(self, data): + self._data.append(data) + + ## + # Opens a new element. + # + # @param tag The element name. + # @param attrib A dictionary containing element attributes. + # @return The opened element. + # @defreturn Element + + def start(self, tag, attrs): + self._flush() + self._last = elem = self._factory(tag, attrs) + if self._elem: + self._elem[-1].append(elem) + self._elem.append(elem) + self._tail = 0 + return elem + + ## + # Closes the current element. + # + # @param tag The element name. + # @return The closed element. + # @defreturn Element + + def end(self, tag): + self._flush() + self._last = self._elem.pop() + assert self._last.tag == tag,\ + "end tag mismatch (expected %s, got %s)" % ( + self._last.tag, tag) + self._tail = 1 + return self._last + +## +# Element structure builder for XML source data, based on the +# expat parser. +# +# @keyparam target Target object. If omitted, the builder uses an +# instance of the standard {@link #TreeBuilder} class. +# @keyparam html Predefine HTML entities. This flag is not supported +# by the current implementation. +# @see #ElementTree +# @see #TreeBuilder + +class XMLTreeBuilder: + + def __init__(self, html=0, target=None): + try: + from xml.parsers import expat + except ImportError: + raise ImportError( + "No module named expat; use SimpleXMLTreeBuilder instead" + ) + self._parser = parser = expat.ParserCreate(None, "}") + if target is None: + target = TreeBuilder() + self._target = target + self._names = {} # name memo cache + # callbacks + parser.DefaultHandlerExpand = self._default + parser.StartElementHandler = self._start + parser.EndElementHandler = self._end + parser.CharacterDataHandler = self._data + # let expat do the buffering, if supported + try: + self._parser.buffer_text = 1 + except AttributeError: + pass + # use new-style attribute handling, if supported + try: + self._parser.ordered_attributes = 1 + self._parser.specified_attributes = 1 + parser.StartElementHandler = self._start_list + except AttributeError: + pass + encoding = None + if not parser.returns_unicode: + encoding = "utf-8" + # target.xml(encoding, None) + self._doctype = None + self.entity = {} + + def _fixtext(self, text): + # convert text string to ascii, if possible + try: + return _encode(text, "ascii") + except UnicodeError: + return text + + def _fixname(self, key): + # expand qname, and convert name string to ascii, if possible + try: + name = self._names[key] + except KeyError: + name = key + if "}" in name: + name = "{" + name + self._names[key] = name = self._fixtext(name) + return name + + def _start(self, tag, attrib_in): + fixname = self._fixname + tag = fixname(tag) + attrib = {} + for key, value in attrib_in.items(): + attrib[fixname(key)] = self._fixtext(value) + return self._target.start(tag, attrib) + + def _start_list(self, tag, attrib_in): + fixname = self._fixname + tag = fixname(tag) + attrib = {} + if attrib_in: + for i in range(0, len(attrib_in), 2): + attrib[fixname(attrib_in[i])] = self._fixtext(attrib_in[i+1]) + return self._target.start(tag, attrib) + + def _data(self, text): + return self._target.data(self._fixtext(text)) + + def _end(self, tag): + return self._target.end(self._fixname(tag)) + + def _default(self, text): + prefix = text[:1] + if prefix == "&": + # deal with undefined entities + try: + self._target.data(self.entity[text[1:-1]]) + except KeyError: + from xml.parsers import expat + raise expat.error( + "undefined entity %s: line %d, column %d" % + (text, self._parser.ErrorLineNumber, + self._parser.ErrorColumnNumber) + ) + elif prefix == "<" and text[:9] == "": + self._doctype = None + return + text = string.strip(text) + if not text: + return + self._doctype.append(text) + n = len(self._doctype) + if n > 2: + type = self._doctype[1] + if type == "PUBLIC" and n == 4: + name, type, pubid, system = self._doctype + elif type == "SYSTEM" and n == 3: + name, type, system = self._doctype + pubid = None + else: + return + if pubid: + pubid = pubid[1:-1] + self.doctype(name, pubid, system[1:-1]) + self._doctype = None + + ## + # Handles a doctype declaration. + # + # @param name Doctype name. + # @param pubid Public identifier. + # @param system System identifier. + + def doctype(self, name, pubid, system): + pass + + ## + # Feeds data to the parser. + # + # @param data Encoded data. + + def feed(self, data): + self._parser.Parse(data, 0) + + ## + # Finishes feeding data to the parser. + # + # @return An element structure. + # @defreturn Element + + def close(self): + self._parser.Parse("", 1) # end of data + tree = self._target.close() + del self._target, self._parser # get rid of circular references + return tree diff --git a/resources/lib/elementtree/__init__.py b/resources/lib/elementtree/__init__.py new file mode 100644 index 0000000..ef8d14c --- /dev/null +++ b/resources/lib/elementtree/__init__.py @@ -0,0 +1,30 @@ +# $Id: __init__.py 1821 2004-06-03 16:57:49Z fredrik $ +# elementtree package + +# -------------------------------------------------------------------- +# The ElementTree toolkit is +# +# Copyright (c) 1999-2004 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- diff --git a/resources/lib/file.py b/resources/lib/file.py new file mode 100644 index 0000000..a169e28 --- /dev/null +++ b/resources/lib/file.py @@ -0,0 +1,6 @@ +class FakeFile: + def poll(self): + return False + + def communicate(self): + return '', '' \ No newline at end of file diff --git a/resources/lib/makemkv.py b/resources/lib/makemkv.py new file mode 100644 index 0000000..3217121 --- /dev/null +++ b/resources/lib/makemkv.py @@ -0,0 +1,148 @@ +import subprocess, settings, brlog, tempfile, os, threading, xbmcgui, urllib, re, xbmc +from functools import partial + +def killMkvOnPlaybackEnd(pid): + dialog = xbmcgui.Dialog() #@UndefinedVariable + dialog.ok("playback ended, about to kill %d"%(pid)) + + +class MakeMkvInteraction: + def __init__(self): + self.settings = settings.BluRaySettings() + self.progress = ProgressInfo() + self.progress.start() + self.log = brlog.BrLog('makemkvinteraction') + + def discList(self): + # First stop any old one + self.killMkv() + tmpf = tempfile.NamedTemporaryFile(delete=True) + progressFile = self.progress.startProgress() + sp = os.system(r'%s -r --cache=1 --progress=%s --messages=%s info disc:10' %(self.settings.mkvLocation, progressFile, tmpf.name)) + + tmpf = open(tmpf.name) + content = tmpf.read() + tmpf.close() + disclist = [x for x in content.splitlines() if x.startswith('DRV:')] + readableDiscs = list() + for item in disclist: + info = [x.replace('"','') for x in item[4:].split(',')] + if len(info[5]) > 0: + readableDiscs.append( (info[0],info[5])) + return readableDiscs + + def startStream(self, disc): + self.log.info("Starting stream on disc %s with local url %s" %(disc, self.settings.rootURL)) + # Then fork the makemkv process + # progressFile = self.progress.startProgress(times = 2) + return self.__runandregistershutdown('%s -r --cache=128 stream disc:%s' %(self.settings.mkvLocation, disc)) + + def startFileStream(self, choice): + type = '' + if re.search("BDMV.index.bdmv", choice) : + # Treat as file + type = 'file' + choice = choice[:-15] + elif re.search("BDMV.MovieObject.bdmv", choice) : + # Treat as file + type = 'file' + choice = choice[:-21] + else: + # Treat as iso + type = 'iso' + + # Check if the file is reachable through the filesystem, to prevent errors with smb:// shares etc. + if not os.path.exists(choice) : + dialog = xbmcgui.Dialog() #@UndefinedVariable + dialog.ok("Info", _(50073)) + return False + + return self.__runandregistershutdown('"%s" -r --cache=128 stream \'%s:%s\'' %(self.settings.mkvLocation, type, choice)) + + + def __runandregistershutdown(self, mkvStart): + result = self.__runmkvstream(mkvStart) + if result >= 0: + xbmc.Player().onPlayBackStopped(partial(killMkvOnPlaybackEnd, result)) + return True + else: + return False + + + def __runmkvstream(self, mkvStart): + self.log.info('Starting %s' %(mkvStart)) + # First stop any old one + self.killMkv() + timeSlept = 0 + proc = subprocess.Popen(mkvStart, shell=True) + # Then wait for the stream to come up + while True: + try: + urllib.urlretrieve(self.settings.rootURL) + return proc.pid + except IOError: + pass + if proc.poll() : + if proc.proc != 0 : + self.message(_(50070)) + return -1 + xbmc.sleep(1000) + timeSlept = timeSlept + 1 + if timeSlept > self.settings.waitTimeOut : + return -1 + + + def killMkv(self): + # Linux + try : + self.log.info('attempting linux kill of makemkvcon') #@UndefinedVariable + subprocess.call('killall -9 makemkvcon', shell=True) + self.log.info('Linux call successful') #@UndefinedVariable + except: + pass + + #Windows. + try : + self.log.info('attempting windows kill of makemkvcon') #@UndefinedVariable + subprocess.call('taskkill /F /IM makemkvcon.exe', shell=True) + self.log.info('Windows call successful') #@UndefinedVariable + except: + pass + + +class ProgressInfo(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self.progressFile = None + self.showing = False + self.dialog = None + + def run(self): + while True: + if self.showing: + busy = True + maxprg = 1000 + current = 0 + + while self.showing: + line = self.progressFile.readline() + if line.startswith('PRGV:'): + progress = line[4:].split(',') + maxprg = int(progress[2]) + current= int(progress[1]) + self.dialog.update(int(float(current) / float(maxprg) * 1000.0) ) + if current >= maxprg: + self.times = self.times - 1 + + if self.times == 0: + self.dialog.close() + self.showing = False + + def startProgress(self, times = 1): + self.dialog = xbmcgui.DialogProgress() + self.dialog.create('XBMC', '', '') + self.progressFile = tempfile.NamedTemporaryFile() + self.times = times + self.showing = True + return self.progressFile.name + diff --git a/resources/lib/mkvparser.py b/resources/lib/mkvparser.py new file mode 100644 index 0000000..deeb001 --- /dev/null +++ b/resources/lib/mkvparser.py @@ -0,0 +1,56 @@ +import urllib, re, elementtree.ElementTree as ET + +class BrowseHandler: + def __init__(self) : + self.catchCharData = False + self.keyColumn = False + self.map = {} + self.lastKey = '' + self.lastVal = '' + self.titleMap = {} + + def start(self, url, title = 'none'): + # Initialize all locals + self.catchCharData = False + self.keyColumn = False + self.map[url] = {} + self.currMap = self.map[url] + self.lastKey = '' + self.lastVal = '' + filename, headers = urllib.urlretrieve(url) + del headers + + XML = ET.parse(filename) + elems = XML.getiterator(tag='{http://www.w3.org/1999/xhtml}td') + for each in elems: + self.keyColumn = not self.keyColumn + if self.keyColumn: + self.lastKey = each.text + else: + txt = '' + # Check for href: + refs = each.getiterator(tag='{http://www.w3.org/1999/xhtml}a') + for ref in refs: + txt = ref.get('href') + + if txt == '' : + txt = each.text + + self.currMap[self.lastKey] = txt + + # Now do some processing: + for k, v in self.map[url].iteritems() : + if k == 'titles': + # go straight ahead and parse some more: + self.start(v) + if re.search('title\d+', k) : + self.titleMap[k] = {} + self.start(v, k) + if title != 'none': + if k == 'duration': + self.titleMap[title]['duration'] = v + elif k == 'file0': + self.titleMap[title]['file'] = v + elif k == 'chaptercount': + self.titleMap[title]['chaptercount'] = v + diff --git a/resources/lib/settings.py b/resources/lib/settings.py new file mode 100644 index 0000000..db1eb41 --- /dev/null +++ b/resources/lib/settings.py @@ -0,0 +1,86 @@ +import xbmcplugin, xbmcaddon +import sys +import urllib +import brlog + +__scriptID__ = sys.modules[ "__main__" ].__scriptID__ + + +class BluRaySettings: + def __init__(self): + addon = xbmcaddon.Addon(__scriptID__) + self.log = brlog.BrLog('settings') + self.log.info('reading settings') #@UndefinedVariable + + params = self.getParams() + self.paramUrl = self.getParam(params, 'url') + self.paramName = self.getParam(params, "name") + self.paramMode = self.getIntParam(params, "mode") + self.autoPlay = self.getBoolParam(params, "autoplay") + self.disc = self.getParam(params, "disc") + self.local = self.paramMode <> 3 + self.ipAddress = addon.getSetting('ip_address') #@UndefinedVariable + self.portNumber = addon.getSetting('port_number') #@UndefinedVariable + if (self.local): + #Make sure local means 127.0.0.1 ... + self.ipAddress = '127.0.0.1' + else : + #Remote so use that portnumber + self.portNumber = addon.getSetting('remote_port_number') #@UndefinedVariable + + self.mkvLocation = addon.getSetting('mkvlocation') #@UndefinedVariable + self.rootURL = 'http://%s:%s/' % (self.ipAddress, self.portNumber) + self.waitTimeOut = int(addon.getSetting('wait_timeout')) #@UndefinedVariable + + # Sections: + self.enableDisc = addon.getSetting('support_disc') == "true" #@UndefinedVariable + self.enableFile = addon.getSetting('support_fileselect') == "true" #@UndefinedVariable + self.enableRemote = addon.getSetting('support_remote') == "true" #@UndefinedVariable + + def getParam(self, params, name): + try: + result = params[name] + result = urllib.unquote_plus(result) + return result + except: + return None + + def getIntParam (self, params, name): + try: + param = self.getParam(params,name) + self.log.debug(name + ' = ' + param) + return int(param) + except: + return None + + def getBoolParam (self, params, name): + try: + param = self.getParam(params,name) + self.log.debug(name + ' = ' + param) + return 'True' == param + except: + return None + + def getParams(self): + try: + param=[] + paramstring=sys.argv[2] + self.log.info('raw param string: ' + paramstring) + if len(paramstring)>=2: + params=sys.argv[2] + cleanedparams=params.replace('?','') + if (params[len(params)-1]=='/'): + params=params[0:len(params)-2] + pairsofparams=cleanedparams.split('&') + param={} + for i in range(len(pairsofparams)): + splitparams={} + splitparams=pairsofparams[i].split('=') + if (len(splitparams))==2: + param[splitparams[0]]=splitparams[1] + return param + except: + return [] + + def showSettings(self): + xbmcaddon.Addon(__scriptID__).openSettings(sys.argv[ 0 ]) #@UndefinedVariable diff --git a/resources/settings.xml b/resources/settings.xml new file mode 100644 index 0000000..4596004 --- /dev/null +++ b/resources/settings.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/service.py b/service.py new file mode 100644 index 0000000..7348819 --- /dev/null +++ b/service.py @@ -0,0 +1,41 @@ +import xbmc, xbmcgui, subprocess, os, sys, urllib, re +import xbmcplugin, xbmcaddon + + +__scriptID__ = "plugin.makemkvbluray" +__addon__ = xbmcaddon.Addon(__scriptID__) + +# Shared resources +BASE_RESOURCE_PATH = os.path.join( __addon__.getAddonInfo('path'), "resources" ) +sys.path.append( os.path.join( BASE_RESOURCE_PATH, "lib" ) ) + +import makemkv + +__language__ = __addon__.getLocalizedString +_ = sys.modules[ "__main__" ].__language__ + +import settings, file, mkvparser, brlog, makemkv + +_log = brlog.BrLog('tracker service') + +_log.info('Starting the BluRay tracker service') #@UndefinedVariable + +class MyPlayer(xbmc.Player): + def __init__(self): + xbmc.Player.__init__(self) + self.makemkv = makemkv.MakeMkvInteraction() + + def onPlayBackStopped(self): + _log.info('Playback stopped, trying to kill makemkv') + xbmc.executebuiltin('Notification("MakeMkv", "Playback ended, stopping makemkv")') + self.makemkv.killMkv() + + def onPlayBackStarted(self): + _log.info('Playback started') + + + +myPlayer = MyPlayer() + +while (not xbmc.abortRequested): + xbmc.sleep(4)