]> git.pld-linux.org Git - packages/griffith.git/blame - Kodi.py
- release 2 (by relup.sh)
[packages/griffith.git] / Kodi.py
CommitLineData
489423d6
ER
1# -*- coding: utf-8 -*-
2
3__revision__ = '$Id: $'
4
a48bca53 5# Copyright (c) 2015-2016 Elan Ruusamäe <glen@pld-linux.org>
489423d6
ER
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
24from plugins.imp import ImportPlugin as IP
25import os
26import gutils
489423d6 27from lxml import etree
0a532283 28import tempfile
2a7f8b8c 29import requests
2c3ca387 30from movie import TempFileCleanup
489423d6 31import logging
2c3ca387 32
489423d6
ER
33log = logging.getLogger("Griffith")
34
35"""
880cc34c
ER
36Supports importing videodb.xml exported as single file from Kodi/XBMC.
37
38http://kodi.wiki/view/Import-export_library
39http://kodi.wiki/view/HOW-TO:Backup_the_video_library
40
489423d6
ER
41See lib/plugins/imp/__init__.py for workflow how importer invokes methods from importer plugins
42"""
43class 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
0a532283 55 poster_file = None
489423d6
ER
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',
5411d3e0 64 'runtime': 'runtime',
489423d6
ER
65 'rating': 'rating',
66 'plot': 'plot',
67 'director': 'director',
68 'studio': 'studio',
69 'country': 'country',
91cfb43d 70 'classification': 'mpaa',
489423d6
ER
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
91cfb43d
ER
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
489423d6
ER
88 def initialize(self):
89 if not IP.initialize(self):
90 return False
91 self.edit = False
2c3ca387
ER
92 self.tempfiles = TempFileCleanup()
93
8a0f2423
ER
94 # try requests cache if available
95 try:
96 import requests_cache
97 requests_cache.install_cache('kodi_cache')
98 except:
99 pass
100
489423d6
ER
101 return True
102
103 def set_source(self, name):
104 IP.set_source(self, name)
105 self.filename = name
106 self.fileversion = self.read_fileversion()
107 if self.fileversion == None:
108 gutils.error(_('The format of the file is not supported.'))
109 return False
110 return True
111
112 def clear(self):
113 """clear plugin state before next source file"""
114 IP.clear(self)
115 if self.xml:
116 self.xml = None
117 self.fileversion = None
118 self.items = None
119 self.itemindex = 0
0a532283
ER
120 if self.poster_file:
121 os.unlink(self.poster_file)
122 self.poster_file = None
489423d6
ER
123
124 def destroy(self):
125 """close all resources"""
2c3ca387 126 self.tempfiles = None
489423d6
ER
127 IP.destroy(self)
128
129 def read_fileversion(self):
130 version = None
131 try:
132 self.xml = etree.parse(self.filename)
133 version = self.xml.xpath('/videodb/version')[0].text
134 except Exception, e:
135 log.error(str(e))
136 log.info('Found file version %s' % version)
137 return version
138
139 def count_movies(self):
140 """Returns number of movies in file which is about to be imported"""
141 if not self.xml:
142 log.error('No XML object')
143 return 0
144
145 count = 0
146
147 try:
148 count = int(self.xml.xpath('count(/videodb/movie)'))
149 except Exception, e:
150 log.exception(e)
151
152 log.info('%s movies for import' % count)
153 return count
154
155 def get_movie_details(self):
156 """Returns dictionary with movie details"""
157 if not self.xml:
158 log.error('XML not opened')
159 return None
160
161 if not self.items:
162 self.items = self.xml.xpath('/videodb/movie')
163 self.itemindex = 0
164
165 # don't bother for empty db (shouldn't happen really)
166 if not self.items or len(self.items) < 1:
167 return None
168
169 # no more items
170 if self.itemindex >= len(self.items):
171 return None
172
173 item = self.items[self.itemindex]
174
175 # fill details
176 details = {}
489423d6
ER
177
178 # if playcount set, means it's seen
179 details['seen'] = int(item.find('playcount').text) > 0
180
a48bca53
ER
181 # import only "seen" movies
182 if not details['seen']:
a48bca53 183 # increment for next iteration
2a7f8b8c
ER
184 self.itemindex = self.itemindex + 1
185 # importer will skip movie without title and original title
a48bca53
ER
186 return details
187
188 for k,v in self.field_map.items():
189 details[k] = item.findtext(v)
190
489423d6
ER
191 # genre can be multiple items, join by comma
192 details['genre'] = ', '.join(n.text for n in item.findall('genre'))
193
5411d3e0
ER
194 # build text for 'cast' field
195 cast = []
196 for actor in item.findall('actor'):
197 cast.append('%s as %s' % (actor.findtext('name'), actor.findtext('role')))
198
199 if cast:
200 details['cast'] = "\n".join(cast)
201
91cfb43d
ER
202 # put rest of information into notes field
203 notes = []
204 for k,v in self.notes_map.items():
205 v = item.findtext(v)
206 if v:
207 notes.append('%s: %s' % (k, v))
208
209 # credits can have multiple values, handle separately
210 credits = ', '.join(n.text for n in item.findall('credits'))
211 if credits:
212 notes.append(_('Credits: %s') % credits)
213
214 if notes:
215 details['notes'] = "\n".join(notes)
216
be7895e5
ER
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']))
220
78f08559
ER
221 # handle poster
222 # take first <thumb aspect="poster"> element
223 posters = item.xpath('thumb[@aspect="poster"]')
224 if posters:
0a532283
ER
225 self.poster_file = self.grab_url(posters[0].get('preview'), prefix = 'poster_', suffix = '.jpg')
226 details['image'] = self.poster_file
78f08559 227
489423d6
ER
228 # increment for next iteration
229 self.itemindex = self.itemindex + 1
230
231 return details
0a532283
ER
232
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):
0a532283 237 (fd, local_file) = tempfile.mkstemp(suffix=suffix, prefix=prefix)
2c3ca387 238 log.debug("Downloading: %s as %s" % (url, local_file))
0a532283 239 try:
2a7f8b8c
ER
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):
244 os.write(fd, chunk)
0a532283
ER
245 os.close(fd)
246
2a7f8b8c
ER
247 except requests.exceptions.RequestException as e:
248 log.error("HTTP Error: %s: %s" % (e, url))
0a532283
ER
249 return None
250 else:
2a7f8b8c 251 log.debug("Downloaded: %s" % url)
2c3ca387 252 self.tempfiles._tempfiles.append(local_file)
0a532283
ER
253 return local_file
254
255 # we get here with an exception, cleanup and return None
256 os.unlink(local_file)
257 return None
This page took 0.060646 seconds and 5 git commands to generate.