]> git.pld-linux.org Git - packages/griffith.git/blob - Kodi.py
- release 2 (by relup.sh)
[packages/griffith.git] / Kodi.py
1 # -*- coding: utf-8 -*-
2
3 __revision__ = '$Id: $'
4
5 # Copyright (c) 2015-2016 Elan Ruusamäe <glen@pld-linux.org>
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 import requests
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         # try requests cache if available
95         try:
96             import requests_cache
97             requests_cache.install_cache('kodi_cache')
98         except:
99             pass
100
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
120             if self.poster_file:
121                 os.unlink(self.poster_file)
122                 self.poster_file = None
123
124     def destroy(self):
125         """close all resources"""
126         self.tempfiles = None
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 = {}
177
178         # if playcount set, means it's seen
179         details['seen'] = int(item.find('playcount').text) > 0
180
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
186             return details
187
188         for k,v in self.field_map.items():
189             details[k] = item.findtext(v)
190
191         # genre can be multiple items, join by comma
192         details['genre'] = ', '.join(n.text for n in item.findall('genre'))
193
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
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
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
221         # handle poster
222         # take first <thumb aspect="poster"> element
223         posters = item.xpath('thumb[@aspect="poster"]')
224         if posters:
225             self.poster_file = self.grab_url(posters[0].get('preview'), prefix = 'poster_', suffix = '.jpg')
226             details['image'] = self.poster_file
227
228         # increment for next iteration
229         self.itemindex = self.itemindex + 1
230
231         return details
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):
237         (fd, local_file) = tempfile.mkstemp(suffix=suffix, prefix=prefix)
238         log.debug("Downloading: %s as %s" % (url, local_file))
239         try:
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)
245             os.close(fd)
246
247         except requests.exceptions.RequestException as e:
248             log.error("HTTP Error: %s: %s" % (e, url))
249             return None
250         else:
251             log.debug("Downloaded: %s" % url)
252             self.tempfiles._tempfiles.append(local_file)
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.106184 seconds and 4 git commands to generate.