]>
Commit | Line | Data |
---|---|---|
489423d6 ER |
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 | |
489423d6 | 27 | from lxml import etree |
0a532283 ER |
28 | import tempfile |
29 | from urllib2 import urlopen, URLError, HTTPError | |
2c3ca387 | 30 | from movie import TempFileCleanup |
489423d6 | 31 | import logging |
2c3ca387 | 32 | |
489423d6 ER |
33 | log = logging.getLogger("Griffith") |
34 | ||
35 | """ | |
880cc34c ER |
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 | ||
489423d6 ER |
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 | |
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 | ||
489423d6 ER |
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 | |
0a532283 ER |
113 | if self.poster_file: |
114 | os.unlink(self.poster_file) | |
115 | self.poster_file = None | |
489423d6 ER |
116 | |
117 | def destroy(self): | |
118 | """close all resources""" | |
2c3ca387 | 119 | self.tempfiles = None |
489423d6 ER |
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 | ||
5411d3e0 ER |
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 | ||
91cfb43d ER |
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 | ||
78f08559 ER |
202 | # handle poster |
203 | # take first <thumb aspect="poster"> element | |
204 | posters = item.xpath('thumb[@aspect="poster"]') | |
205 | if posters: | |
0a532283 ER |
206 | self.poster_file = self.grab_url(posters[0].get('preview'), prefix = 'poster_', suffix = '.jpg') |
207 | details['image'] = self.poster_file | |
78f08559 | 208 | |
489423d6 ER |
209 | # increment for next iteration |
210 | self.itemindex = self.itemindex + 1 | |
211 | ||
212 | return details | |
0a532283 ER |
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): | |
0a532283 | 218 | (fd, local_file) = tempfile.mkstemp(suffix=suffix, prefix=prefix) |
2c3ca387 | 219 | log.debug("Downloading: %s as %s" % (url, local_file)) |
0a532283 ER |
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: | |
2c3ca387 | 232 | self.tempfiles._tempfiles.append(local_file) |
0a532283 ER |
233 | return local_file |
234 | ||
235 | # we get here with an exception, cleanup and return None | |
236 | os.unlink(local_file) | |
237 | return None |