Coverage for mymeco/kodi/movie.py: 35%
232 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-12-11 22:07 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-12-11 22:07 +0000
1"""Movie module."""
2# pylint: disable=too-many-public-methods
3# pylint: disable=too-many-instance-attributes
4import typing
5from lxml import etree
7from mymeco.files.video import Video
10RatingType = typing.Mapping[str, typing.Union[int, bool]]
11ActorType = typing.Mapping[str, typing.Union[str, int]]
14class Movie:
15 """
16 Movie class.
18 Aim to group all information that could be stored in a movie entry of Kodi
19 NFO files, bind to an xml formatter.
20 """
22 def __init__(self, movieid=None, tmdb=None):
23 """Init a new movie data structure."""
24 self.__data = {}
25 if tmdb is not None and movieid is not None:
26 self.__fill_data_from_tmdb(tmdb, movieid)
28 def __fill_data_from_tmdb(self, tmdb, movieid):
29 staffs = tmdb.get_credits(movieid)
30 details = tmdb.get_movie(movieid)
32 self.title = details.get('title', None)
33 self.originaltitle = details.get('original_title', None)
34 self.rating('tmdb',
35 details.get('vote_average', None),
36 votes=details.get('vote_count', None), default=True)
37 self.plot = details.get('overview', None)
38 self.tagline = details.get('tagline', None)
39 self.runtime = str(details.get('runtime', None))
40 self.poster = details.get('poster_path', None)
41 self.add_uniqueid('tmdb', str(movieid))
42 self.add_uniqueid('imdb', details.get('imdb_id', None))
43 for genre in details.get('genres', []):
44 self.add_genre(genre['name'])
45 for country in details.get('production_countries', []):
46 self.add_country(country['name'])
47 # print(set(staff['department'] for staff in staffs['crew']))
48 # print(set(
49 # staff['job']
50 # for staff in staffs['crew']
51 # if staff['department'] == 'Directing'
52 # ))
53 for staff in staffs['crew']:
54 if staff['department'] == 'Writing':
55 self.add_writter(staff['name'])
56 if (
57 staff['department'] == 'Directing' and
58 staff['job'] == 'Director'
59 ):
60 self.add_director(staff['name'])
61 self.premiered = details.get('release_date', None)
62 for studio in details.get('production_companies', []):
63 self.add_studio(studio['name'])
64 for staff in staffs['cast']:
65 self.add_actor(
66 staff['name'],
67 staff['character'],
68 staff['order'],
69 staff['profile_path']
70 )
72 @property
73 def title(self) -> typing.Optional[str]:
74 """Get localized title of movie."""
75 return self.__data.get('title', None)
77 @title.setter
78 def title(self, title: str):
79 if title is not None:
80 self.__data['title'] = title
82 @property
83 def originaltitle(self) -> typing.Optional[str]:
84 """Get original title of movie."""
85 return self.__data.get('originaltitle', self.title)
87 @originaltitle.setter
88 def originaltitle(self, originaltitle: str):
89 if originaltitle is not None:
90 self.__data['originaltitle'] = originaltitle
92 @property
93 def sorttitle(self) -> typing.Optional[str]:
94 """Get sortable title of movie."""
95 return self.__data.get('sorttitle', self.title)
97 @sorttitle.setter
98 def sorttitle(self, sorttitle: str):
99 if sorttitle is not None:
100 self.__data['sorttitle'] = sorttitle
102 @property
103 def ratings(self) -> typing.Mapping[str, RatingType]:
104 """Get list of all ratings associated to this movie."""
105 return self.__data['ratings']
107 def rating(self, name: str, values: int, **kwargs: RatingType):
108 """Store a new rating for given movie."""
109 if values is None:
110 return
111 if 'ratings' not in self.__data:
112 self.__data['ratings'] = {}
113 self.__data['ratings'][name] = {
114 'values': values,
115 'votes': kwargs.get('votes', None),
116 'max': kwargs.get('max', 10),
117 'default': kwargs.get('default', False)
118 }
120 @property
121 def plot(self) -> typing.Optional[str]:
122 """Get movie plot."""
123 return self.__data.get('plot', None)
125 @plot.setter
126 def plot(self, plot):
127 if plot is not None:
128 self.__data['plot'] = plot
130 @property
131 def tagline(self):
132 """Get movie tagline."""
133 return self.__data.get('tagline', None)
135 @tagline.setter
136 def tagline(self, tagline):
137 if tagline is not None:
138 self.__data['tagline'] = tagline
140 @property
141 def runtime(self) -> typing.Optional[int]:
142 """Get runtime in minutes."""
143 return self.__data.get('runtime', None)
145 @runtime.setter
146 def runtime(self, minutes: typing.Optional[int]):
147 if minutes is not None:
148 self.__data['runtime'] = minutes
150 @property
151 def poster(self) -> typing.Optional[str]:
152 """Get movie poster URL."""
153 return self.__data.get('thumb', {'poster': None}).get('poster', None)
155 @poster.setter
156 def poster(self, poster: typing.Optional[str]):
157 if poster is None:
158 return
160 if 'thumb' not in self.__data:
161 self.__data['thumb'] = {}
163 self.__data['thumb']['poster'] = poster
165 @property
166 def uniqueid(self) -> typing.Optional[typing.Mapping[str, str]]:
167 """Retrieve list of unique ids."""
168 return self.__data.get('uniqueid', None)
170 def add_uniqueid(self, idtype: str, value: str):
171 """Add a new uniqueid in movie."""
172 if value is None:
173 return
175 if 'uniqueid' not in self.__data:
176 self.__data['uniqueid'] = {}
178 self.__data['uniqueid'][idtype] = value
180 @property
181 def genre(self) -> typing.Iterable[str]:
182 """Get list of genres."""
183 return self.__data.get('genre', {})
185 def add_genre(self, genre: str):
186 """Add new genre to movie."""
187 if genre is not None:
188 if 'genre' not in self.__data:
189 self.__data['genre'] = {genre}
190 else:
191 self.__data['genre'].add(genre)
193 @property
194 def country(self) -> typing.Iterable[str]:
195 """Get list of production countries."""
196 return self.__data.get('country', {})
198 def add_country(self, country: str):
199 """Add new production country."""
200 if country is not None:
201 if 'country' not in self.__data:
202 self.__data['country'] = {country}
203 else:
204 self.__data['country'].add(country)
206 @property
207 def writter(self) -> typing.Iterable[str]:
208 """Get list of writter."""
209 return self.__data.get('credits', {})
211 def add_writter(self, writter: str):
212 """Add new writter."""
213 if writter is not None:
214 if 'credits' not in self.__data:
215 self.__data['credits'] = {writter}
216 else:
217 self.__data['credits'].add(writter)
219 @property
220 def director(self) -> typing.Iterable[str]:
221 """Get list of director."""
222 return self.__data.get('director', {})
224 def add_director(self, director: str):
225 """Add new director."""
226 if director is not None:
227 if 'director' not in self.__data:
228 self.__data['director'] = {director}
229 else:
230 self.__data['director'].add(director)
232 @property
233 def premiered(self) -> typing.Optional[str]:
234 """Get premierered date."""
235 return self.__data.get('premiered', None)
237 @premiered.setter
238 def premiered(self, release_date: typing.Optional[str]):
239 if release_date is not None:
240 self.__data['premiered'] = release_date
242 @property
243 def studio(self) -> typing.Sequence[str]:
244 """Get production studios."""
245 return self.__data.get('studio', {})
247 def add_studio(self, studio: typing.Optional[str]):
248 """Add new studio."""
249 if studio is not None:
250 if 'studio' in self.__data:
251 self.__data['studio'].add(studio)
252 else:
253 self.__data['studio'] = {studio}
255 @property
256 def actors(self) -> typing.Sequence[ActorType]:
257 """Get actors."""
258 return self.__data.get('actor', [])
260 def add_actor(self, name: str, role: str, order: int, thumb: str):
261 """Add new actor."""
262 if 'actor' not in self.__data:
263 self.__data['actor'] = []
265 self.__data['actor'].append({
266 'name': name,
267 'role': role,
268 'order': order,
269 'thumb': thumb
270 })
272 @property
273 def technical(self):
274 """Retrieve technical information on movie file."""
275 return self.__data.get('fileinfo', {})
277 def set_technical(self, video: Video):
278 """Add technical information."""
279 self.__data['fileinfo'] = {
280 'streamdetails': {
281 'video': video.video,
282 'audio': video.audio,
283 'subtitle': video.subtitle
284 }
285 }
287 def nfo(self, filehandle):
288 """Write NFO data in stream-compatible object."""
289 root = etree.Element('movie')
290 dispatch = {
291 'ratings': self._handle_ratings,
292 'thumb': self._handle_thumb,
293 'uniqueid': self._handle_uniqueid,
294 'actor': self._handle_actors,
295 'fileinfo': self._handle_fileinfo,
296 }
297 for key, value in self.__data.items():
298 if key in dispatch:
299 dispatch[key](root, value)
300 continue
301 if isinstance(value, (str, int)):
302 element = etree.SubElement(root, key)
303 element.text = value
304 continue
305 if isinstance(value, (list, set)):
306 for item in value:
307 element = etree.SubElement(root, key)
308 element.text = item
309 continue
311 filehandle.write(etree.tounicode(root, pretty_print=True))
313 @staticmethod
314 def _handle_ratings(movie, values):
315 element = etree.SubElement(movie, 'ratings')
316 for name, content in values.items():
317 rating = etree.SubElement(element, 'rating')
318 rating.attrib['name'] = name
319 if 'max' in content:
320 rating.attrib['max'] = str(content['max'])
321 if 'default' in content:
322 rating.attrib['default'] = str(content['default'])
323 value = etree.SubElement(rating, 'value')
324 value.text = str(content['values'])
325 if 'votes' in content:
326 votes = etree.SubElement(rating, 'votes')
327 votes.text = str(content['votes'])
329 @staticmethod
330 def _handle_thumb(movie, values):
331 for aspect, url in values.items():
332 thumb = etree.SubElement(movie, 'thumb', aspect=aspect)
333 thumb.text = url
335 @staticmethod
336 def _handle_uniqueid(movie, values):
337 default = True
338 for idtype, value in values.items():
339 uniqueid = etree.SubElement(
340 movie, 'uniqueid', type=idtype, default=str(default)
341 )
342 uniqueid.text = value
343 default = False
345 @staticmethod
346 def _handle_actors(movie, values):
347 for actor in values:
348 element = etree.SubElement(movie, 'actor')
349 for item in ('name', 'role', 'order', 'thumb'):
350 subelement = etree.SubElement(element, item)
351 subelement.text = str(actor[item])
353 @staticmethod
354 def _handle_fileinfo(movie, values):
355 fileinfo = etree.SubElement(movie, 'fileinfo')
356 streamdetails = etree.SubElement(fileinfo, 'streamdetails')
357 for video in values['streamdetails']['video']:
358 stream = etree.SubElement(streamdetails, 'video')
359 for item in (
360 'codec', 'aspect', 'width', 'height', 'durationinseconds'
361 ):
362 etree.SubElement(stream, item).text = str(video[item])
363 for audio in values['streamdetails']['audio']:
364 stream = etree.SubElement(streamdetails, 'audio')
365 for item in (
366 'codec', 'language', 'channels'
367 ):
368 etree.SubElement(stream, item).text = str(audio[item])
369 for sub in values['streamdetails']['subtitle']:
370 stream = etree.SubElement(streamdetails, 'subtitle')
371 for item in ('language',):
372 etree.SubElement(stream, item).text = str(sub[item])