Author: duncan
Date: Sun Dec  2 06:12:21 2007
New Revision: 10170

Log:
[ 1841768 ] Movie covers grabber for french people
New video plug-in from Sylvain Fabre added


Added:
   branches/rel-1-7/freevo/src/video/plugins/moviecovers.py   (contents, props 
changed)
   branches/rel-1/freevo/src/video/plugins/moviecovers.py   (contents, props 
changed)
Modified:
   branches/rel-1-7/freevo/ChangeLog
   branches/rel-1-7/freevo/Docs/CREDITS
   branches/rel-1/freevo/ChangeLog
   branches/rel-1/freevo/Docs/CREDITS

Modified: branches/rel-1-7/freevo/ChangeLog
==============================================================================
--- branches/rel-1-7/freevo/ChangeLog   (original)
+++ branches/rel-1-7/freevo/ChangeLog   Sun Dec  2 06:12:21 2007
@@ -17,6 +17,7 @@
 --------------------------------
 
  * New Alsa mixer 2 plug-in (F#1832948)
+ * New French movie cover grabber (F#1841768)
  * New gphoto plug-in, in contrib/runtime (F#1838260)
  * New six channel mixer and idlebar plug-in (F#1833749)
  * Updated French translation (F#1832751)

Modified: branches/rel-1-7/freevo/Docs/CREDITS
==============================================================================
--- branches/rel-1-7/freevo/Docs/CREDITS        (original)
+++ branches/rel-1-7/freevo/Docs/CREDITS        Sun Dec  2 06:12:21 2007
@@ -66,6 +66,9 @@
 Occasional contributors:
 ------------------------
 
+Sylvain FABRE <centraladmin at lahiette.com>
+o French movie cover grabber
+
 Thorsten Pferdekaemper <thorsten.pferdekaemper at t-online.de>
 o gphoto plug-in
 

Added: branches/rel-1-7/freevo/src/video/plugins/moviecovers.py
==============================================================================
--- (empty file)
+++ branches/rel-1-7/freevo/src/video/plugins/moviecovers.py    Sun Dec  2 
06:12:21 2007
@@ -0,0 +1,616 @@
+#if 0 /*
+# -----------------------------------------------------------------------
+# moviecovers.py - Plugin for MOVIECOVERS support
+# -----------------------------------------------------------------------
+# $Id$
+# Version: 080607_01
+#
+# Notes: Moviecovers plugin. You can add Moviecovers.com informations for 
video items
+#        with the plugin
+#        Activate with: plugin.activate('video.moviecovers')
+#        And add the following lines to your configuration file:
+#          MOVIECOVERS_AUTOACCEPT_SINGLE_HIT = True
+#          It uses also directly the variables:
+#              - ALLOCINE_REMOVE_FROM_LABEL
+#              - ALLOCINE_REMOVE_FROM_SEARCHSTRING 
+#          as the same words shall be removed also for moviecovers. See 
allocine.py for a desription 
+#          of these variables.
+#        You can also set allocine_search on a key (e.g. '1') by setting
+#        EVENTS['menu']['1'] = Event(MENU_CALL_ITEM_ACTION, 
arg='moviecovers_search_or_cover_search')
+#
+# Todo:  - Update existing FXD file
+#        - DVD/VCD support (discset ??)
+#
+# Author : S. FABRE for Biboobox, http://www.lahiette.com/biboobox
+#
+# -----------------------------------------------------------------------
+# $Log$
+# -----------------------------------------------------------------------
+# Freevo - A Home Theater PC framework
+# Copyright (C) 2002 Krister Lagerstrom, et al. 
+# Please see the file freevo/Docs/CREDITS for a complete list of authors.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
+# CHANTABILITY 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, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# ----------------------------------------------------------------------- */
+#endif
+
+import re
+import socket
+socket.setdefaulttimeout(30.0)
+import urllib, urllib2, urlparse, commands
+import sys
+import codecs
+import os
+import traceback
+
+import menu
+import config
+import plugin
+import time
+from util import htmlenties2txt
+from util import fxdparser
+from gui.PopupBox import PopupBox
+
+# headers for urllib2
+txdata = None
+txheaders = {
+    'User-Agent': 'freevo (%s)' % sys.platform,
+    'Accept-Language': 'fr-fr',
+}
+
+class PluginInterface(plugin.ItemPlugin):
+    def __init__(self, license=None):
+        """Initialise class instance"""
+
+        # these are considered as private variables - don't mess with them 
unless
+        # no other choise is given
+        # fyi, the other choice always exists : add a subroutine or ask :)
+        if not config.USE_NETWORK:
+            self.reason = 'no network'
+            return
+        plugin.ItemPlugin.__init__(self)
+
+    def initmyself(self):
+        self.isdiscset = False
+        self.title = ''
+        self.info = {}
+
+        self.image = None # full path image filename
+        self.image_urls = [] # possible image url list
+        self.image_url  = None # final image url
+
+        self.fxdfile = None # filename, full path, WITHOUT extension
+
+        self.append = False
+        self.device = None
+        self.regexp = None
+        self.mpl_global_opt = None
+        self.media_id = None
+        self.file_opts = []
+        self.video = []
+        self.variant = []
+        self.parts = []
+        self.var_mplopt = []
+        self.var_names = []
+
+        #image_url_handler stuff
+        self.image_url_handler = {}
+
+    def searchMoviecovers(self, name):
+        """name (string), returns id list
+        Search for name and returns an id list with tuples:
+            (id , name, year, type)"""
+        # Clean internal variables
+        self.initmyself()
+        self.moviecovers_id_list = []
+
+        regexp_tag = re.compile('<[^>]+>', re.I)
+        #print "Request with: %s" % urllib.quote(name)
+        url = 
'http://www.moviecovers.com/multicrit.html?titre=%s&slow=1&listes=1' % 
(urllib.quote(name))
+        req = urllib2.Request(url, txdata, txheaders)
+        searchstring = name
+
+        try:
+            response = urllib2.urlopen(req)
+        except urllib2.HTTPError, error:
+            raise FxdMoviecovers_Net_Error("Moviecovers unreachable : " + 
error)
+            exit
+
+        regexp_getall   = re.compile('.*<LI><A 
href="(/film/titre_.*)">(.*?)</A>.*([0-9]{4}).*', re.I)
+
+        for line in response.read().split("\n"):
+            #print line
+            m = regexp_getall.match(line)
+            if m:
+                #print "Found film in line : %s" % line
+                link = m.group(1)
+                name = m.group(2)
+                year = m.group(3)
+                self.moviecovers_id_list += [ ( link, name, year, 'Movies' ) ]
+
+        return self.moviecovers_id_list
+
+    def guessMoviecovers(self, filename, label=False):
+      """Guess possible Moviecovers movies from filename. Same return as 
searchMoviecovers"""
+      name = filename
+
+      for r in config.ALLOCINE_REMOVE_FROM_LABEL:
+        name  = re.sub(r, '', name.lower())
+
+      name  = vfs.basename(vfs.splitext(name)[0])
+      name  = re.sub('([a-z])([A-Z])', point_maker, name)
+      name  = re.sub('([a-zA-Z])([0-9])', point_maker, name)
+      name  = re.sub('([0-9])([a-zA-Z])', point_maker, name.lower())
+      name  = re.sub(',', ' ', name)
+
+      parts = re.split("[\._' -]", name)
+      name = ''
+
+      for p in parts:
+        if not p.lower() in config.ALLOCINE_REMOVE_FROM_SEARCHSTRING and \
+            not re.search('[^0-9A-Za-z]', p):
+          # originally: not re.search(p, '[A-Za-z]'):
+          # not sure what's meant with that
+          name += '%s ' % p
+
+      return self.searchMoviecovers(name)
+
+    def getMoviecoversPage(self, url):
+        """url
+        Set an moviecovers number for object, and fetch data"""
+        self.myurl = 'http://www.moviecovers.com/' + 
urllib.quote(urllib.unquote(url))
+        #print "Now trying to get %s" % self.myurl
+        req = urllib2.Request(self.myurl, txdata, txheaders)
+
+        try:
+            idpage = urllib2.urlopen(req)
+        except urllib2.HTTPError, error:
+            raise FxdAllocine_Net_Error("Moviecovers.com unreachable" + error)
+            return None
+
+        #print "Response : %s" % idpage.read()
+        self.parsedata(idpage, id)
+        idpage.close()
+
+
+    def setFxdFile(self, fxdfilename = None, overwrite = False):
+        """
+        setFxdFile (string, full path)
+        Set fxd file to write to, may be omitted, may be an existing file
+        (data will be added) unless overwrite = True
+        """
+
+        if fxdfilename:
+            if vfs.splitext(fxdfilename)[1] == '.fxd':
+                self.fxdfile = vfs.splitext(fxdfilename)[0]
+            else: self.fxdfile = fxdfilename
+
+        else:
+            if self.isdiscset == True:
+                self.fxdfile = vfs.join(config.OVERLAY_DIR, 'disc-set',
+                                        self.getmedia_id(self.device))
+            else:
+                self.fxdfile = vfs.splitext(file)[0]
+
+        if overwrite == False:
+            try:
+                vfs.open(self.fxdfile + '.fxd')
+                self.append = True
+            except:
+                pass
+        else:
+            self.append = False
+
+        # XXX: add this back in without using parseMovieFile
+        # if self.append == True and \
+        #    parseMovieFile(self.fxdfile + '.fxd', None, []) == []:
+        #     raise FxdAllocine_XML_Error("FXD file to be updated is invalid, 
please correct it.")
+
+        if not vfs.isdir(vfs.dirname(self.fxdfile)):
+            if vfs.dirname(self.fxdfile):
+                os.makedirs(vfs.dirname(self.fxdfile))
+
+    def moviecovers_get_disc_searchstring(self, item):
+        name  = item.media.label
+        name  = re.sub('([a-z])([A-Z])', point_maker, name)
+        name  = re.sub('([a-zA-Z])([0-9])', point_maker, name)
+        name  = re.sub('([0-9])([a-zA-Z])', point_maker, name.lower())
+        parts = re.split("[\._' -]", name)
+
+        name = ''
+        for p in parts:
+            if p:
+                name += '%s ' % p
+        if name:
+            return name[:-1]
+        else:
+            return ''
+
+
+    def actions(self, item):
+        self.item = item
+
+        if item.type == 'video' and (not item.files or not 
item.files.fxd_file):
+            if item.mode == 'file' or (item.mode in ('dvd', 'vcd') and \
+                                       item.info.has_key('tracks') and not \
+                                       item.media):
+                self.disc_set = False
+                return [ ( self.moviecovers_search , _('Search Moviecovers.com 
for this file'),
+                           'moviecovers_search_or_cover_search') ]
+
+            elif item.mode in ('dvd', 'vcd') and item.info.has_key('tracks'):
+                self.disc_set = True
+                s = self.moviecovers_get_disc_searchstring(self.item)
+                if s:
+                    return [ ( self.moviecovers_search , _('Search 
Moviecovers.com for [%s]') % s,
+                               'moviecovers_search_or_cover_search') ]
+
+        if item.type == 'dir' and item.media and 
item.media.mountdir.find(item.dir) == 0:
+            self.disc_set = True
+            s = self.moviecovers_get_disc_searchstring(self.item)
+            if s:
+                return [ ( self.moviecovers_search , _('Search Moviecovers.com 
for [%s]') % s,
+                           'moviecovers_search_or_cover_search') ]
+        return []
+
+
+    def moviecovers_search(self, arg=None, menuw=None):
+        """
+        search moviecovers for this item
+        """
+        box = PopupBox(text=_('Searching Moviecovers.com...'))
+        box.show()
+
+        items = []
+
+        try:
+            duplicates = []
+            if self.disc_set:
+                self.searchstring = self.item.media.label
+            else:
+                self.searchstring = self.item.name
+
+            for id,name,year,type in self.guessMoviecovers(self.searchstring, 
self.disc_set):
+                try:
+                    for i in self.item.parent.play_items:
+                        if i.name == name:
+                            if not i in duplicates:
+                                duplicates.append(i)
+                except:
+                    pass
+                items.append(menu.MenuItem('%s (%s, %s)' % 
(htmlenties2txt(name), year, type),
+                                           self.moviecovers_create_fxd, (id, 
year)))
+        except:
+            box.destroy()
+            box = PopupBox(text=_('Connection error : Probably connection 
timeout, try again'))
+            box.show()
+            time.sleep(2)
+            box.destroy()
+            traceback.print_exc()
+            return
+
+        box.destroy()
+        if config.MOVIECOVERS_AUTOACCEPT_SINGLE_HIT and len(items) == 1:
+            self.moviecovers_create_fxd(arg=items[0].arg, menuw=menuw)
+            return
+
+        if items:
+            moviemenu = menu.Menu(_('MOVIECOVERS Query'), items)
+            menuw.pushmenu(moviemenu)
+            return
+
+        box = PopupBox(text=_('No information available from Moviecovers.com'))
+        box.show()
+        time.sleep(2)
+        box.destroy()
+        return
+
+
+    def moviecovers_menu_back(self, menuw):
+        """
+        check how many menus we have to go back to see the item
+        """
+        import directory
+
+        # check if we have to go one menu back (called directly) or
+        # two (called from the item menu)
+        back = 1
+        if menuw.menustack[-2].selected != self.item:
+            back = 2
+
+        # maybe we called the function directly because there was only one
+        # entry and we called it with an event
+        if menuw.menustack[-1].selected == self.item:
+            back = 0
+
+        # update the directory
+        if directory.dirwatcher:
+            directory.dirwatcher.scan()
+
+        # go back in menustack
+        for i in range(back):
+            menuw.delete_menu()
+
+
+    def moviecovers_create_fxd(self, arg=None, menuw=None):
+        """
+        create fxd file for the item
+        """
+        box = PopupBox(text=_('Getting data, it can be long as we download a 
ZIP file'))
+        box.show()
+
+        #if this exists we got a cdrom/dvdrom
+        if self.item.media and self.item.media.devicename:
+            devicename = self.item.media.devicename
+        else:
+            devicename = None
+
+        self.getMoviecoversPage(arg[0])
+
+        if self.disc_set:
+            self.setDiscset(devicename, None)
+        else:
+            self.setFxdFile(os.path.splitext(self.item.filename)[0])
+
+        self.writeFxd()
+        self.moviecovers_menu_back(menuw)
+        box.destroy()
+
+
+    def moviecovers_add_to_fxd(self, arg=None, menuw=None):
+        """
+        add item to fxd file
+        BROKEN, PLEASE FIX
+        """
+
+        #if this exists we got a cdrom/dvdrom
+        if self.item.media and self.item.media.devicename:
+            devicename = self.item.media.devicename
+        else: devicename = None
+
+        self.setFxdFile(arg[0].fxd_file)
+
+        if self.item.mode in ('dvd', 'vcd'):
+            self.setDiscset(devicename, None)
+        else:
+            num = len(self.video) + 1
+            if arg[1] == 'variant':
+                part = makePart('Variant %d' % num, 'f%s' % num)
+
+                if self.variant:
+                    part = [ makePart('Variant 1', 'f1'), part ]
+
+                self.setVariants(part)
+
+        self.writeFxd()
+        self.moviecovers_menu_back(menuw)
+
+    def writeFxd(self):
+        """Write fxd file"""
+        #if fxdfile is empty, set it yourself
+        if not self.fxdfile:
+            self.setFxdFile()
+
+        try:
+            #should we add to an existing file?
+            if self.append == True :
+                if self.isdiscset == True:
+                    self.update_discset()
+                else: self.update_movie()
+            else:
+                #fetch images
+                self.fetch_image()
+                #should we write a disc-set ?
+                if self.isdiscset == True:
+                    self.write_discset()
+                else:
+                    self.write_movie()
+
+            #check fxd
+            # XXX: add this back in without using parseMovieFile
+            # if parseMovieFile(self.fxdfile + '.fxd', None, []) == []:
+            #     raise FxdImdb_XML_Error("""FXD file generated is invalid, 
please "+
+            #                             "post bugreport, tracebacks and fxd 
file.""")
+
+        except (IOError, FxdMoviecovers_IO_Error), error:
+            raise FxdMoviecovers_IO_Error('error saving the file: %s' % 
str(error))
+
+
+    def setDiscset(self, device, regexp, *file_opts, **mpl_global_opt):
+        """
+        device (string), regexp (string), file_opts (tuple 
(mplayer-opts,file)),
+        mpl_global_opt (string)
+        Set media is dvd/vcd,
+        """
+        if len(self.video) != 0 or len(self.variant) != 0:
+            raise FxdMoviecovers_XML_Error("<movie> already used, can't use 
both "+
+                                    "<movie> and <disc-set>")
+
+        self.isdiscset = True
+        if (not device and not regexp) or (device and regexp):
+            raise FxdMoviecovers_XML_Error("Can't use both media-id and 
regexp")
+
+        self.device = device
+        self.regexp = regexp
+
+        for opts in file_opts:
+            self.file_opts += [ opts ]
+
+        if mpl_global_opt and 'mplayer_opt' in mpl_global_opt:
+            self.mpl_global_opt = (mpl_global_opt['mplayer_opt'])
+
+
+    def isDiscset(self):
+        """Check if fxd file describes a disc-set, returns 1 for true, 0 for 
false
+        None for invalid file"""
+        try:
+            file = vfs.open(self.fxdfile + '.fxd')
+        except IOError:
+            return None
+
+        content = file.read()
+        file.close()
+        if content.find('</disc-set>') != -1: return 1
+        return 0
+
+#------ private functions below .....
+
+    def write_discset(self):
+        """Write a <disc-set> to a fresh file"""
+        print "Discset not supported for the moment... Sorry"
+
+    def write_fxd_copyright(self, fxd, node):
+        fxd.setcdata(node, "The information in this file are from 
Moviecovers.com.\n"+
+                           "Please visit http://www.moviecovers.com for more 
informations.\n")
+        fxd.add(fxd.XMLnode('source', [('url', "%s" % self.myurl)] ), node, 0)
+
+    def write_fxd_video(self, fxd, node):
+        fxd.setattr(node, 'title', self.title )
+        fxd.add(fxd.XMLnode('cover-img', (('source', self.image_url), ("test", 
"test")), self.image ), node, 0)
+        videonode = fxd.XMLnode('video')
+        fxd.add(videonode, node)
+        fxd.add(fxd.XMLnode('file', [('id', 'f1')], 
os.path.basename(self.item.filename) ), videonode, 0)
+        infonode = fxd.XMLnode('info')
+        fxd.add(infonode, node)
+        if self.info:
+            for k in self.info.keys():
+                fxd.add(fxd.XMLnode(k, [], self.info[k]), infonode, 0)
+
+    def write_movie(self):
+        """Write <movie> to fxd file"""
+        try:
+            parser = fxdparser.FXD(self.fxdfile + '.fxd')
+            parser.set_handler('copyright', self.write_fxd_copyright, 'w', 
True)
+            parser.set_handler('movie', self.write_fxd_video, 'w', True)
+            parser.save()
+        except:
+            print "fxd file %s corrupt" % self.fxdfile
+            traceback.print_exc()
+
+    def update_movie(self):
+        """Updates an existing file, adds exftra dvd|vcd|file and variant 
tags"""
+        print "Update not supported for the moment... Sorry"
+
+    def update_discset(self):
+        """Updates an existing file, adds extra disc in discset"""
+        print "Update not supported for the moment... Sorry"
+
+    def parsedata(self, results, id=0):
+        """results (moviecovers html page), allocine_id
+        Returns tuple of (title, info(dict), image_urls)"""
+
+        dvd = 0
+        inside_plot = None
+
+        regexp_zipfile = re.compile('.*<A href="(/getzip.html/.*?)">.*', re.I)
+        for line in results.read().split("\n"):
+            m = regexp_zipfile.match(line)
+            if m:
+                myurl2 = 'www.moviecovers.com' + m.group(1)
+                break
+
+        # Chain shell commands to get the zip file for data
+        self.tmppath = '/tmp/moviecovers/'
+        self.tmpfile = self.tmppath + 'file.zip'
+        commands.getstatusoutput('rm -r -f ' + self.tmppath)
+        commands.getstatusoutput('mkdir '+ self.tmppath )
+        #print "Get : %s " % urllib.quote(urllib.unquote(myurl2))
+        commands.getstatusoutput('wget ' + 
urllib.quote(urllib.unquote(myurl2)) + ' -O ' + self.tmpfile )
+        (status, output) = commands.getstatusoutput('cd '+ self.tmppath+'; 
unzip -o '+ self.tmpfile )
+
+        # Now copy
+        regexp_jpegfile = re.compile('.*jpg', re.I)
+        regexp_filmfile = re.compile('.*film', re.I)
+        for file in os.listdir(self.tmppath):
+            #print "File found : " + file
+            m = regexp_jpegfile.match(file)
+            if m:
+                #print "Found JPEG file : %s" % file
+                self.imagefile = self.tmppath + vfs.basename(file)
+            m = regexp_filmfile.match(file)
+            if m:
+                #print "Found FILM file : %s" % file
+                self.filmfile = self.tmppath + vfs.basename(file)
+
+        # Now parse the film file
+        file = open(self.filmfile )
+        # File format is :
+            #Title
+            #Real
+            #Year
+            #Pays
+            #Genre
+            #Dur?e
+            #Acteurs
+            #Synopsis
+        lineno = 0
+        self.info['plot'] = ''
+        for line in file.read().split("\n"):
+            if lineno == 0: self.title = line
+            if lineno == 1: self.info['director'] = line
+            if lineno == 2: self.info['year'] = line
+            if lineno == 3: self.info['country']   = line
+            if lineno == 4: self.info['genre'] = line
+            if lineno == 5: self.info['runtime']  = line
+            if lineno == 6: self.info['actor']= line
+            if lineno > 6:
+                self.info['plot'] += line
+            lineno += 1
+
+        return (self.title, self.info, self.imagefile)
+
+    def fetch_image(self):
+        """Fetch the best image"""
+        image_len = 0
+
+        if (len(self.imagefile) == 0): # No images
+            return
+
+        self.image = (self.fxdfile + '.jpg')
+        commands.getstatusoutput('cp -f "' + self.imagefile + '" "'+ 
self.image + '"')
+
+        self.image = vfs.basename(self.image)
+
+        print "Downloaded cover image from Moviecovers.com"
+        print "Freevo knows nothing about the copyright of this image, please"
+        print "go to Moviecovers.com to check for more informations about 
private."
+        print "use of this image"
+
+class Error(Exception):
+    """Base class for exceptions in Allocine_Fxd"""
+    def __str__(self):
+        return self.message
+    def __init__(self, message):
+        self.message = message
+
+class FxdMoviecovers_Error(Error):
+    """used to raise exceptions"""
+    pass
+
+class FxdMoviecovers_XML_Error(Error):
+    """used to raise exceptions"""
+    pass
+
+class FxdMoviecovers_IO_Error(Error):
+    """used to raise exceptions"""
+    pass
+
+class FxdMoviecovers_Net_Error(Error):
+    """used to raise exceptions"""
+    pass
+
+def point_maker(matching):
+    return '%s.%s' % (matching.groups()[0], matching.groups()[1])

Modified: branches/rel-1/freevo/ChangeLog
==============================================================================
--- branches/rel-1/freevo/ChangeLog     (original)
+++ branches/rel-1/freevo/ChangeLog     Sun Dec  2 06:12:21 2007
@@ -22,6 +22,7 @@
 --------------------------------
 
  * New Alsa mixer 2 plug-in (F#1832948)
+ * New French movie cover grabber (F#1841768)
  * New gphoto plug-in, in contrib/runtime (F#1838260)
  * New six channel mixer and idlebar plug-in (F#1833749)
  * Updated French translation (F#1832751)

Modified: branches/rel-1/freevo/Docs/CREDITS
==============================================================================
--- branches/rel-1/freevo/Docs/CREDITS  (original)
+++ branches/rel-1/freevo/Docs/CREDITS  Sun Dec  2 06:12:21 2007
@@ -66,6 +66,9 @@
 Occasional contributors:
 ------------------------
 
+Sylvain FABRE <centraladmin at lahiette.com>
+o French movie cover grabber
+
 Thorsten Pferdekaemper <thorsten.pferdekaemper at t-online.de>
 o gphoto plug-in
 

Added: branches/rel-1/freevo/src/video/plugins/moviecovers.py
==============================================================================
--- (empty file)
+++ branches/rel-1/freevo/src/video/plugins/moviecovers.py      Sun Dec  2 
06:12:21 2007
@@ -0,0 +1,616 @@
+#if 0 /*
+# -----------------------------------------------------------------------
+# moviecovers.py - Plugin for MOVIECOVERS support
+# -----------------------------------------------------------------------
+# $Id$
+# Version: 080607_01
+#
+# Notes: Moviecovers plugin. You can add Moviecovers.com informations for 
video items
+#        with the plugin
+#        Activate with: plugin.activate('video.moviecovers')
+#        And add the following lines to your configuration file:
+#          MOVIECOVERS_AUTOACCEPT_SINGLE_HIT = True
+#          It uses also directly the variables:
+#              - ALLOCINE_REMOVE_FROM_LABEL
+#              - ALLOCINE_REMOVE_FROM_SEARCHSTRING 
+#          as the same words shall be removed also for moviecovers. See 
allocine.py for a desription 
+#          of these variables.
+#        You can also set allocine_search on a key (e.g. '1') by setting
+#        EVENTS['menu']['1'] = Event(MENU_CALL_ITEM_ACTION, 
arg='moviecovers_search_or_cover_search')
+#
+# Todo:  - Update existing FXD file
+#        - DVD/VCD support (discset ??)
+#
+# Author : S. FABRE for Biboobox, http://www.lahiette.com/biboobox
+#
+# -----------------------------------------------------------------------
+# $Log$
+# -----------------------------------------------------------------------
+# Freevo - A Home Theater PC framework
+# Copyright (C) 2002 Krister Lagerstrom, et al. 
+# Please see the file freevo/Docs/CREDITS for a complete list of authors.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
+# CHANTABILITY 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, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# ----------------------------------------------------------------------- */
+#endif
+
+import re
+import socket
+socket.setdefaulttimeout(30.0)
+import urllib, urllib2, urlparse, commands
+import sys
+import codecs
+import os
+import traceback
+
+import menu
+import config
+import plugin
+import time
+from util import htmlenties2txt
+from util import fxdparser
+from gui.PopupBox import PopupBox
+
+# headers for urllib2
+txdata = None
+txheaders = {
+    'User-Agent': 'freevo (%s)' % sys.platform,
+    'Accept-Language': 'fr-fr',
+}
+
+class PluginInterface(plugin.ItemPlugin):
+    def __init__(self, license=None):
+        """Initialise class instance"""
+
+        # these are considered as private variables - don't mess with them 
unless
+        # no other choise is given
+        # fyi, the other choice always exists : add a subroutine or ask :)
+        if not config.USE_NETWORK:
+            self.reason = 'no network'
+            return
+        plugin.ItemPlugin.__init__(self)
+
+    def initmyself(self):
+        self.isdiscset = False
+        self.title = ''
+        self.info = {}
+
+        self.image = None # full path image filename
+        self.image_urls = [] # possible image url list
+        self.image_url  = None # final image url
+
+        self.fxdfile = None # filename, full path, WITHOUT extension
+
+        self.append = False
+        self.device = None
+        self.regexp = None
+        self.mpl_global_opt = None
+        self.media_id = None
+        self.file_opts = []
+        self.video = []
+        self.variant = []
+        self.parts = []
+        self.var_mplopt = []
+        self.var_names = []
+
+        #image_url_handler stuff
+        self.image_url_handler = {}
+
+    def searchMoviecovers(self, name):
+        """name (string), returns id list
+        Search for name and returns an id list with tuples:
+            (id , name, year, type)"""
+        # Clean internal variables
+        self.initmyself()
+        self.moviecovers_id_list = []
+
+        regexp_tag = re.compile('<[^>]+>', re.I)
+        #print "Request with: %s" % urllib.quote(name)
+        url = 
'http://www.moviecovers.com/multicrit.html?titre=%s&slow=1&listes=1' % 
(urllib.quote(name))
+        req = urllib2.Request(url, txdata, txheaders)
+        searchstring = name
+
+        try:
+            response = urllib2.urlopen(req)
+        except urllib2.HTTPError, error:
+            raise FxdMoviecovers_Net_Error("Moviecovers unreachable : " + 
error)
+            exit
+
+        regexp_getall   = re.compile('.*<LI><A 
href="(/film/titre_.*)">(.*?)</A>.*([0-9]{4}).*', re.I)
+
+        for line in response.read().split("\n"):
+            #print line
+            m = regexp_getall.match(line)
+            if m:
+                #print "Found film in line : %s" % line
+                link = m.group(1)
+                name = m.group(2)
+                year = m.group(3)
+                self.moviecovers_id_list += [ ( link, name, year, 'Movies' ) ]
+
+        return self.moviecovers_id_list
+
+    def guessMoviecovers(self, filename, label=False):
+      """Guess possible Moviecovers movies from filename. Same return as 
searchMoviecovers"""
+      name = filename
+
+      for r in config.ALLOCINE_REMOVE_FROM_LABEL:
+        name  = re.sub(r, '', name.lower())
+
+      name  = vfs.basename(vfs.splitext(name)[0])
+      name  = re.sub('([a-z])([A-Z])', point_maker, name)
+      name  = re.sub('([a-zA-Z])([0-9])', point_maker, name)
+      name  = re.sub('([0-9])([a-zA-Z])', point_maker, name.lower())
+      name  = re.sub(',', ' ', name)
+
+      parts = re.split("[\._' -]", name)
+      name = ''
+
+      for p in parts:
+        if not p.lower() in config.ALLOCINE_REMOVE_FROM_SEARCHSTRING and \
+            not re.search('[^0-9A-Za-z]', p):
+          # originally: not re.search(p, '[A-Za-z]'):
+          # not sure what's meant with that
+          name += '%s ' % p
+
+      return self.searchMoviecovers(name)
+
+    def getMoviecoversPage(self, url):
+        """url
+        Set an moviecovers number for object, and fetch data"""
+        self.myurl = 'http://www.moviecovers.com/' + 
urllib.quote(urllib.unquote(url))
+        #print "Now trying to get %s" % self.myurl
+        req = urllib2.Request(self.myurl, txdata, txheaders)
+
+        try:
+            idpage = urllib2.urlopen(req)
+        except urllib2.HTTPError, error:
+            raise FxdAllocine_Net_Error("Moviecovers.com unreachable" + error)
+            return None
+
+        #print "Response : %s" % idpage.read()
+        self.parsedata(idpage, id)
+        idpage.close()
+
+
+    def setFxdFile(self, fxdfilename = None, overwrite = False):
+        """
+        setFxdFile (string, full path)
+        Set fxd file to write to, may be omitted, may be an existing file
+        (data will be added) unless overwrite = True
+        """
+
+        if fxdfilename:
+            if vfs.splitext(fxdfilename)[1] == '.fxd':
+                self.fxdfile = vfs.splitext(fxdfilename)[0]
+            else: self.fxdfile = fxdfilename
+
+        else:
+            if self.isdiscset == True:
+                self.fxdfile = vfs.join(config.OVERLAY_DIR, 'disc-set',
+                                        self.getmedia_id(self.device))
+            else:
+                self.fxdfile = vfs.splitext(file)[0]
+
+        if overwrite == False:
+            try:
+                vfs.open(self.fxdfile + '.fxd')
+                self.append = True
+            except:
+                pass
+        else:
+            self.append = False
+
+        # XXX: add this back in without using parseMovieFile
+        # if self.append == True and \
+        #    parseMovieFile(self.fxdfile + '.fxd', None, []) == []:
+        #     raise FxdAllocine_XML_Error("FXD file to be updated is invalid, 
please correct it.")
+
+        if not vfs.isdir(vfs.dirname(self.fxdfile)):
+            if vfs.dirname(self.fxdfile):
+                os.makedirs(vfs.dirname(self.fxdfile))
+
+    def moviecovers_get_disc_searchstring(self, item):
+        name  = item.media.label
+        name  = re.sub('([a-z])([A-Z])', point_maker, name)
+        name  = re.sub('([a-zA-Z])([0-9])', point_maker, name)
+        name  = re.sub('([0-9])([a-zA-Z])', point_maker, name.lower())
+        parts = re.split("[\._' -]", name)
+
+        name = ''
+        for p in parts:
+            if p:
+                name += '%s ' % p
+        if name:
+            return name[:-1]
+        else:
+            return ''
+
+
+    def actions(self, item):
+        self.item = item
+
+        if item.type == 'video' and (not item.files or not 
item.files.fxd_file):
+            if item.mode == 'file' or (item.mode in ('dvd', 'vcd') and \
+                                       item.info.has_key('tracks') and not \
+                                       item.media):
+                self.disc_set = False
+                return [ ( self.moviecovers_search , _('Search Moviecovers.com 
for this file'),
+                           'moviecovers_search_or_cover_search') ]
+
+            elif item.mode in ('dvd', 'vcd') and item.info.has_key('tracks'):
+                self.disc_set = True
+                s = self.moviecovers_get_disc_searchstring(self.item)
+                if s:
+                    return [ ( self.moviecovers_search , _('Search 
Moviecovers.com for [%s]') % s,
+                               'moviecovers_search_or_cover_search') ]
+
+        if item.type == 'dir' and item.media and 
item.media.mountdir.find(item.dir) == 0:
+            self.disc_set = True
+            s = self.moviecovers_get_disc_searchstring(self.item)
+            if s:
+                return [ ( self.moviecovers_search , _('Search Moviecovers.com 
for [%s]') % s,
+                           'moviecovers_search_or_cover_search') ]
+        return []
+
+
+    def moviecovers_search(self, arg=None, menuw=None):
+        """
+        search moviecovers for this item
+        """
+        box = PopupBox(text=_('Searching Moviecovers.com...'))
+        box.show()
+
+        items = []
+
+        try:
+            duplicates = []
+            if self.disc_set:
+                self.searchstring = self.item.media.label
+            else:
+                self.searchstring = self.item.name
+
+            for id,name,year,type in self.guessMoviecovers(self.searchstring, 
self.disc_set):
+                try:
+                    for i in self.item.parent.play_items:
+                        if i.name == name:
+                            if not i in duplicates:
+                                duplicates.append(i)
+                except:
+                    pass
+                items.append(menu.MenuItem('%s (%s, %s)' % 
(htmlenties2txt(name), year, type),
+                                           self.moviecovers_create_fxd, (id, 
year)))
+        except:
+            box.destroy()
+            box = PopupBox(text=_('Connection error : Probably connection 
timeout, try again'))
+            box.show()
+            time.sleep(2)
+            box.destroy()
+            traceback.print_exc()
+            return
+
+        box.destroy()
+        if config.MOVIECOVERS_AUTOACCEPT_SINGLE_HIT and len(items) == 1:
+            self.moviecovers_create_fxd(arg=items[0].arg, menuw=menuw)
+            return
+
+        if items:
+            moviemenu = menu.Menu(_('MOVIECOVERS Query'), items)
+            menuw.pushmenu(moviemenu)
+            return
+
+        box = PopupBox(text=_('No information available from Moviecovers.com'))
+        box.show()
+        time.sleep(2)
+        box.destroy()
+        return
+
+
+    def moviecovers_menu_back(self, menuw):
+        """
+        check how many menus we have to go back to see the item
+        """
+        import directory
+
+        # check if we have to go one menu back (called directly) or
+        # two (called from the item menu)
+        back = 1
+        if menuw.menustack[-2].selected != self.item:
+            back = 2
+
+        # maybe we called the function directly because there was only one
+        # entry and we called it with an event
+        if menuw.menustack[-1].selected == self.item:
+            back = 0
+
+        # update the directory
+        if directory.dirwatcher:
+            directory.dirwatcher.scan()
+
+        # go back in menustack
+        for i in range(back):
+            menuw.delete_menu()
+
+
+    def moviecovers_create_fxd(self, arg=None, menuw=None):
+        """
+        create fxd file for the item
+        """
+        box = PopupBox(text=_('Getting data, it can be long as we download a 
ZIP file'))
+        box.show()
+
+        #if this exists we got a cdrom/dvdrom
+        if self.item.media and self.item.media.devicename:
+            devicename = self.item.media.devicename
+        else:
+            devicename = None
+
+        self.getMoviecoversPage(arg[0])
+
+        if self.disc_set:
+            self.setDiscset(devicename, None)
+        else:
+            self.setFxdFile(os.path.splitext(self.item.filename)[0])
+
+        self.writeFxd()
+        self.moviecovers_menu_back(menuw)
+        box.destroy()
+
+
+    def moviecovers_add_to_fxd(self, arg=None, menuw=None):
+        """
+        add item to fxd file
+        BROKEN, PLEASE FIX
+        """
+
+        #if this exists we got a cdrom/dvdrom
+        if self.item.media and self.item.media.devicename:
+            devicename = self.item.media.devicename
+        else: devicename = None
+
+        self.setFxdFile(arg[0].fxd_file)
+
+        if self.item.mode in ('dvd', 'vcd'):
+            self.setDiscset(devicename, None)
+        else:
+            num = len(self.video) + 1
+            if arg[1] == 'variant':
+                part = makePart('Variant %d' % num, 'f%s' % num)
+
+                if self.variant:
+                    part = [ makePart('Variant 1', 'f1'), part ]
+
+                self.setVariants(part)
+
+        self.writeFxd()
+        self.moviecovers_menu_back(menuw)
+
+    def writeFxd(self):
+        """Write fxd file"""
+        #if fxdfile is empty, set it yourself
+        if not self.fxdfile:
+            self.setFxdFile()
+
+        try:
+            #should we add to an existing file?
+            if self.append == True :
+                if self.isdiscset == True:
+                    self.update_discset()
+                else: self.update_movie()
+            else:
+                #fetch images
+                self.fetch_image()
+                #should we write a disc-set ?
+                if self.isdiscset == True:
+                    self.write_discset()
+                else:
+                    self.write_movie()
+
+            #check fxd
+            # XXX: add this back in without using parseMovieFile
+            # if parseMovieFile(self.fxdfile + '.fxd', None, []) == []:
+            #     raise FxdImdb_XML_Error("""FXD file generated is invalid, 
please "+
+            #                             "post bugreport, tracebacks and fxd 
file.""")
+
+        except (IOError, FxdMoviecovers_IO_Error), error:
+            raise FxdMoviecovers_IO_Error('error saving the file: %s' % 
str(error))
+
+
+    def setDiscset(self, device, regexp, *file_opts, **mpl_global_opt):
+        """
+        device (string), regexp (string), file_opts (tuple 
(mplayer-opts,file)),
+        mpl_global_opt (string)
+        Set media is dvd/vcd,
+        """
+        if len(self.video) != 0 or len(self.variant) != 0:
+            raise FxdMoviecovers_XML_Error("<movie> already used, can't use 
both "+
+                                    "<movie> and <disc-set>")
+
+        self.isdiscset = True
+        if (not device and not regexp) or (device and regexp):
+            raise FxdMoviecovers_XML_Error("Can't use both media-id and 
regexp")
+
+        self.device = device
+        self.regexp = regexp
+
+        for opts in file_opts:
+            self.file_opts += [ opts ]
+
+        if mpl_global_opt and 'mplayer_opt' in mpl_global_opt:
+            self.mpl_global_opt = (mpl_global_opt['mplayer_opt'])
+
+
+    def isDiscset(self):
+        """Check if fxd file describes a disc-set, returns 1 for true, 0 for 
false
+        None for invalid file"""
+        try:
+            file = vfs.open(self.fxdfile + '.fxd')
+        except IOError:
+            return None
+
+        content = file.read()
+        file.close()
+        if content.find('</disc-set>') != -1: return 1
+        return 0
+
+#------ private functions below .....
+
+    def write_discset(self):
+        """Write a <disc-set> to a fresh file"""
+        print "Discset not supported for the moment... Sorry"
+
+    def write_fxd_copyright(self, fxd, node):
+        fxd.setcdata(node, "The information in this file are from 
Moviecovers.com.\n"+
+                           "Please visit http://www.moviecovers.com for more 
informations.\n")
+        fxd.add(fxd.XMLnode('source', [('url', "%s" % self.myurl)] ), node, 0)
+
+    def write_fxd_video(self, fxd, node):
+        fxd.setattr(node, 'title', self.title )
+        fxd.add(fxd.XMLnode('cover-img', (('source', self.image_url), ("test", 
"test")), self.image ), node, 0)
+        videonode = fxd.XMLnode('video')
+        fxd.add(videonode, node)
+        fxd.add(fxd.XMLnode('file', [('id', 'f1')], 
os.path.basename(self.item.filename) ), videonode, 0)
+        infonode = fxd.XMLnode('info')
+        fxd.add(infonode, node)
+        if self.info:
+            for k in self.info.keys():
+                fxd.add(fxd.XMLnode(k, [], self.info[k]), infonode, 0)
+
+    def write_movie(self):
+        """Write <movie> to fxd file"""
+        try:
+            parser = fxdparser.FXD(self.fxdfile + '.fxd')
+            parser.set_handler('copyright', self.write_fxd_copyright, 'w', 
True)
+            parser.set_handler('movie', self.write_fxd_video, 'w', True)
+            parser.save()
+        except:
+            print "fxd file %s corrupt" % self.fxdfile
+            traceback.print_exc()
+
+    def update_movie(self):
+        """Updates an existing file, adds exftra dvd|vcd|file and variant 
tags"""
+        print "Update not supported for the moment... Sorry"
+
+    def update_discset(self):
+        """Updates an existing file, adds extra disc in discset"""
+        print "Update not supported for the moment... Sorry"
+
+    def parsedata(self, results, id=0):
+        """results (moviecovers html page), allocine_id
+        Returns tuple of (title, info(dict), image_urls)"""
+
+        dvd = 0
+        inside_plot = None
+
+        regexp_zipfile = re.compile('.*<A href="(/getzip.html/.*?)">.*', re.I)
+        for line in results.read().split("\n"):
+            m = regexp_zipfile.match(line)
+            if m:
+                myurl2 = 'www.moviecovers.com' + m.group(1)
+                break
+
+        # Chain shell commands to get the zip file for data
+        self.tmppath = '/tmp/moviecovers/'
+        self.tmpfile = self.tmppath + 'file.zip'
+        commands.getstatusoutput('rm -r -f ' + self.tmppath)
+        commands.getstatusoutput('mkdir '+ self.tmppath )
+        #print "Get : %s " % urllib.quote(urllib.unquote(myurl2))
+        commands.getstatusoutput('wget ' + 
urllib.quote(urllib.unquote(myurl2)) + ' -O ' + self.tmpfile )
+        (status, output) = commands.getstatusoutput('cd '+ self.tmppath+'; 
unzip -o '+ self.tmpfile )
+
+        # Now copy
+        regexp_jpegfile = re.compile('.*jpg', re.I)
+        regexp_filmfile = re.compile('.*film', re.I)
+        for file in os.listdir(self.tmppath):
+            #print "File found : " + file
+            m = regexp_jpegfile.match(file)
+            if m:
+                #print "Found JPEG file : %s" % file
+                self.imagefile = self.tmppath + vfs.basename(file)
+            m = regexp_filmfile.match(file)
+            if m:
+                #print "Found FILM file : %s" % file
+                self.filmfile = self.tmppath + vfs.basename(file)
+
+        # Now parse the film file
+        file = open(self.filmfile )
+        # File format is :
+            #Title
+            #Real
+            #Year
+            #Pays
+            #Genre
+            #Dur?e
+            #Acteurs
+            #Synopsis
+        lineno = 0
+        self.info['plot'] = ''
+        for line in file.read().split("\n"):
+            if lineno == 0: self.title = line
+            if lineno == 1: self.info['director'] = line
+            if lineno == 2: self.info['year'] = line
+            if lineno == 3: self.info['country']   = line
+            if lineno == 4: self.info['genre'] = line
+            if lineno == 5: self.info['runtime']  = line
+            if lineno == 6: self.info['actor']= line
+            if lineno > 6:
+                self.info['plot'] += line
+            lineno += 1
+
+        return (self.title, self.info, self.imagefile)
+
+    def fetch_image(self):
+        """Fetch the best image"""
+        image_len = 0
+
+        if (len(self.imagefile) == 0): # No images
+            return
+
+        self.image = (self.fxdfile + '.jpg')
+        commands.getstatusoutput('cp -f "' + self.imagefile + '" "'+ 
self.image + '"')
+
+        self.image = vfs.basename(self.image)
+
+        print "Downloaded cover image from Moviecovers.com"
+        print "Freevo knows nothing about the copyright of this image, please"
+        print "go to Moviecovers.com to check for more informations about 
private."
+        print "use of this image"
+
+class Error(Exception):
+    """Base class for exceptions in Allocine_Fxd"""
+    def __str__(self):
+        return self.message
+    def __init__(self, message):
+        self.message = message
+
+class FxdMoviecovers_Error(Error):
+    """used to raise exceptions"""
+    pass
+
+class FxdMoviecovers_XML_Error(Error):
+    """used to raise exceptions"""
+    pass
+
+class FxdMoviecovers_IO_Error(Error):
+    """used to raise exceptions"""
+    pass
+
+class FxdMoviecovers_Net_Error(Error):
+    """used to raise exceptions"""
+    pass
+
+def point_maker(matching):
+    return '%s.%s' % (matching.groups()[0], matching.groups()[1])

-------------------------------------------------------------------------
SF.Net email is sponsored by: The Future of Linux Business White Paper
from Novell.  From the desktop to the data center, Linux is going
mainstream.  Let it simplify your IT future.
http://altfarm.mediaplex.com/ad/ck/8857-50307-18918-4
_______________________________________________
Freevo-cvslog mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/freevo-cvslog

Reply via email to