1 # -*- coding: utf-8 -*-
3 __revision__ = '$Id: $'
5 # Copyright (c) 2015-2016 Elan Ruusamäe <glen@pld-linux.org>
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.
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.
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
21 # You may use and distribute this software under the terms of the
22 # GNU General Public License, version 2 or later
24 from plugins.imp import ImportPlugin as IP
27 from lxml import etree
30 from movie import TempFileCleanup
33 log = logging.getLogger("Griffith")
36 Supports importing videodb.xml exported as single file from Kodi/XBMC.
38 http://kodi.wiki/view/Import-export_library
39 http://kodi.wiki/view/HOW-TO:Backup_the_video_library
41 See lib/plugins/imp/__init__.py for workflow how importer invokes methods from importer plugins
43 class ImportPlugin(IP):
44 description = 'Kodi/XBMC MovieDB importer'
45 author = 'Elan Ruusamäe'
46 email = 'glen@pld-linux.org'
48 file_filters = 'videodb.xml'
57 # used by get_movie_details method
58 # griffith field => kodi field
59 # griffith field is actual SQL column in 'movies' table
62 'o_title': 'originaltitle',
67 'director': 'director',
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.
77 # rest of the stuff to insert into notes field
80 _('Play count'): 'playcount',
81 _('Date added'): 'dateadded',
82 _('Last played'): 'lastplayed',
83 _('Collection'): 'set',
84 _('Premiered'): 'premiered',
89 if not IP.initialize(self):
92 self.tempfiles = TempFileCleanup()
94 # try requests cache if available
97 requests_cache.install_cache('kodi_cache')
103 def set_source(self, name):
104 IP.set_source(self, name)
106 self.fileversion = self.read_fileversion()
107 if self.fileversion == None:
108 gutils.error(_('The format of the file is not supported.'))
113 """clear plugin state before next source file"""
117 self.fileversion = None
121 os.unlink(self.poster_file)
122 self.poster_file = None
125 """close all resources"""
126 self.tempfiles = None
129 def read_fileversion(self):
132 self.xml = etree.parse(self.filename)
133 version = self.xml.xpath('/videodb/version')[0].text
136 log.info('Found file version %s' % version)
139 def count_movies(self):
140 """Returns number of movies in file which is about to be imported"""
142 log.error('No XML object')
148 count = int(self.xml.xpath('count(/videodb/movie)'))
152 log.info('%s movies for import' % count)
155 def get_movie_details(self):
156 """Returns dictionary with movie details"""
158 log.error('XML not opened')
162 self.items = self.xml.xpath('/videodb/movie')
165 # don't bother for empty db (shouldn't happen really)
166 if not self.items or len(self.items) < 1:
170 if self.itemindex >= len(self.items):
173 item = self.items[self.itemindex]
178 # if playcount set, means it's seen
179 details['seen'] = int(item.find('playcount').text) > 0
181 # import only "seen" movies
182 if not details['seen']:
183 # increment for next iteration
184 self.itemindex = self.itemindex + 1
185 # importer will skip movie without title and original title
188 for k,v in self.field_map.items():
189 details[k] = item.findtext(v)
191 # genre can be multiple items, join by comma
192 details['genre'] = ', '.join(n.text for n in item.findall('genre'))
194 # build text for 'cast' field
196 for actor in item.findall('actor'):
197 cast.append('%s as %s' % (actor.findtext('name'), actor.findtext('role')))
200 details['cast'] = "\n".join(cast)
202 # put rest of information into notes field
204 for k,v in self.notes_map.items():
207 notes.append('%s: %s' % (k, v))
209 # credits can have multiple values, handle separately
210 credits = ', '.join(n.text for n in item.findall('credits'))
212 notes.append(_('Credits: %s') % credits)
215 details['notes'] = "\n".join(notes)
217 # rating needs to be int or tellico will show it as 0 until movie is saved over
218 if details['rating']:
219 details['rating'] = int(float(details['rating']))
222 # take first <thumb aspect="poster"> element
223 posters = item.xpath('thumb[@aspect="poster"]')
225 self.poster_file = self.grab_url(posters[0].get('preview'), prefix = 'poster_', suffix = '.jpg')
226 details['image'] = self.poster_file
228 # increment for next iteration
229 self.itemindex = self.itemindex + 1
233 # grab url, return temp filename with remote file contents
234 # XXX could not figure out how to use griffith own downloader with ui interaction, etc
235 # XXX: grabbing urls while processing import xml blocks the ui
236 def grab_url(self, url, prefix = None, suffix=None):
237 (fd, local_file) = tempfile.mkstemp(suffix=suffix, prefix=prefix)
238 log.debug("Downloading: %s as %s" % (url, local_file))
240 # http://stackoverflow.com/a/13137873/2314626
241 r = requests.get(url, stream=True, timeout=0.1)
242 if r.status_code == 200:
243 for chunk in r.iter_content(1024):
247 except requests.exceptions.RequestException as e:
248 log.error("HTTP Error: %s: %s" % (e, url))
251 log.debug("Downloaded: %s" % url)
252 self.tempfiles._tempfiles.append(local_file)
255 # we get here with an exception, cleanup and return None
256 os.unlink(local_file)