]> git.pld-linux.org Git - packages/griffith.git/blob - Kodi.py
use TempFileCleanup class to cleanup poster files after use
[packages/griffith.git] / Kodi.py
1 # -*- coding: utf-8 -*-
2
3 __revision__ = '$Id: $'
4
5 # Copyright (c) 2015
6
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU Library General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
20
21 # You may use and distribute this software under the terms of the
22 # GNU General Public License, version 2 or later
23
24 from plugins.imp import ImportPlugin as IP
25 import os
26 import gutils
27 from lxml import etree
28 import tempfile
29 from urllib2 import urlopen, URLError, HTTPError
30 from movie import TempFileCleanup
31 import logging
32
33 log = logging.getLogger("Griffith")
34
35 """
36 Supports importing videodb.xml exported as single file from Kodi/XBMC.
37
38 http://kodi.wiki/view/Import-export_library
39 http://kodi.wiki/view/HOW-TO:Backup_the_video_library
40
41 See lib/plugins/imp/__init__.py for workflow how importer invokes methods from importer plugins
42 """
43 class ImportPlugin(IP):
44     description  = 'Kodi/XBMC MovieDB importer'
45     author       = 'Elan Ruusamäe'
46     email        = 'glen@pld-linux.org'
47     version      = '1.0'
48     file_filters = 'videodb.xml'
49     mime_types   = None
50
51     fileversion  = None
52     xml          = None
53     items        = None
54     itemindex    = 0
55     poster_file  = None
56
57     # used by get_movie_details method
58     # griffith field => kodi field
59     # griffith field is actual SQL column in 'movies' table
60     field_map = {
61         'title': 'title',
62         'o_title': 'originaltitle',
63         'year': 'year',
64         'runtime': 'runtime',
65         'rating': 'rating',
66         'plot': 'plot',
67         'director': 'director',
68         'studio': 'studio',
69         'country': 'country',
70         'classification': 'mpaa',
71         # while the trailer field exists, it is not useful for griffith
72         # as it's something like: "plugin://plugin.video.youtube/?action=play_video&videoid=..."
73         # however youtube urls can be probably fixed.
74         'trailer': 'trailer',
75     }
76
77     # rest of the stuff to insert into notes field
78     notes_map = {
79         _('Id') : 'id',
80         _('Play count'): 'playcount',
81         _('Date added'): 'dateadded',
82         _('Last played'): 'lastplayed',
83         _('Collection'): 'set',
84         _('Premiered'): 'premiered',
85         _('Aired'): 'aired',
86     }
87
88     def initialize(self):
89         if not IP.initialize(self):
90             return False
91         self.edit = False
92         self.tempfiles = TempFileCleanup()
93
94         return True
95
96     def set_source(self, name):
97         IP.set_source(self, name)
98         self.filename = name
99         self.fileversion = self.read_fileversion()
100         if self.fileversion == None:
101             gutils.error(_('The format of the file is not supported.'))
102             return False
103         return True
104
105     def clear(self):
106         """clear plugin state before next source file"""
107         IP.clear(self)
108         if self.xml:
109             self.xml = None
110             self.fileversion = None
111             self.items = None
112             self.itemindex = 0
113             if self.poster_file:
114                 os.unlink(self.poster_file)
115                 self.poster_file = None
116
117     def destroy(self):
118         """close all resources"""
119         self.tempfiles = None
120         IP.destroy(self)
121
122     def read_fileversion(self):
123         version = None
124         try:
125             self.xml = etree.parse(self.filename)
126             version = self.xml.xpath('/videodb/version')[0].text
127         except Exception, e:
128             log.error(str(e))
129         log.info('Found file version %s' % version)
130         return version
131
132     def count_movies(self):
133         """Returns number of movies in file which is about to be imported"""
134         if not self.xml:
135             log.error('No XML object')
136             return 0
137
138         count = 0
139
140         try:
141             count = int(self.xml.xpath('count(/videodb/movie)'))
142         except Exception, e:
143             log.exception(e)
144
145         log.info('%s movies for import' % count)
146         return count
147
148     def get_movie_details(self):
149         """Returns dictionary with movie details"""
150         if not self.xml:
151             log.error('XML not opened')
152             return None
153
154         if not self.items:
155             self.items = self.xml.xpath('/videodb/movie')
156             self.itemindex = 0
157
158         # don't bother for empty db (shouldn't happen really)
159         if not self.items or len(self.items) < 1:
160             return None
161
162         # no more items
163         if self.itemindex >= len(self.items):
164             return None
165
166         item = self.items[self.itemindex]
167
168         # fill details
169         details = {}
170         for k,v in self.field_map.items():
171             details[k] = item.findtext(v)
172
173         # if playcount set, means it's seen
174         details['seen'] = int(item.find('playcount').text) > 0
175
176         # genre can be multiple items, join by comma
177         details['genre'] = ', '.join(n.text for n in item.findall('genre'))
178
179         # build text for 'cast' field
180         cast = []
181         for actor in item.findall('actor'):
182             cast.append('%s as %s' % (actor.findtext('name'), actor.findtext('role')))
183
184         if cast:
185             details['cast'] = "\n".join(cast)
186
187         # put rest of information into notes field
188         notes = []
189         for k,v in self.notes_map.items():
190             v = item.findtext(v)
191             if v:
192                 notes.append('%s: %s' % (k, v))
193
194         # credits can have multiple values, handle separately
195         credits = ', '.join(n.text for n in item.findall('credits'))
196         if credits:
197             notes.append(_('Credits: %s') % credits)
198
199         if notes:
200             details['notes'] = "\n".join(notes)
201
202         # handle poster
203         # take first <thumb aspect="poster"> element
204         posters = item.xpath('thumb[@aspect="poster"]')
205         if posters:
206             self.poster_file = self.grab_url(posters[0].get('preview'), prefix = 'poster_', suffix = '.jpg')
207             details['image'] = self.poster_file
208
209         # increment for next iteration
210         self.itemindex = self.itemindex + 1
211
212         return details
213
214     # grab url, return temp filename with remote file contents
215     # XXX could not figure out how to use griffith own downloader with ui interaction, etc
216     # XXX: grabbing urls while processing import xml blocks the ui
217     def grab_url(self, url, prefix = None, suffix=None):
218         (fd, local_file) = tempfile.mkstemp(suffix=suffix, prefix=prefix)
219         log.debug("Downloading: %s as %s" % (url, local_file))
220         try:
221             f = urlopen(url)
222             os.write(fd, f.read())
223             os.close(fd)
224
225         except HTTPError, e:
226             log.error("HTTP Error: %s: %s" % (e.code, url))
227             return None
228         except URLError, e:
229             log.error("URL Error: %s: %s" % (e.reason, url))
230             return None
231         else:
232             self.tempfiles._tempfiles.append(local_file)
233             return local_file
234
235         # we get here with an exception, cleanup and return None
236         os.unlink(local_file)
237         return None
This page took 0.114678 seconds and 3 git commands to generate.