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

1"""Movie module.""" 

2# pylint: disable=too-many-public-methods 

3# pylint: disable=too-many-instance-attributes 

4import typing 

5from lxml import etree 

6 

7from mymeco.files.video import Video 

8 

9 

10RatingType = typing.Mapping[str, typing.Union[int, bool]] 

11ActorType = typing.Mapping[str, typing.Union[str, int]] 

12 

13 

14class Movie: 

15 """ 

16 Movie class. 

17 

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 """ 

21 

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) 

27 

28 def __fill_data_from_tmdb(self, tmdb, movieid): 

29 staffs = tmdb.get_credits(movieid) 

30 details = tmdb.get_movie(movieid) 

31 

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 ) 

71 

72 @property 

73 def title(self) -> typing.Optional[str]: 

74 """Get localized title of movie.""" 

75 return self.__data.get('title', None) 

76 

77 @title.setter 

78 def title(self, title: str): 

79 if title is not None: 

80 self.__data['title'] = title 

81 

82 @property 

83 def originaltitle(self) -> typing.Optional[str]: 

84 """Get original title of movie.""" 

85 return self.__data.get('originaltitle', self.title) 

86 

87 @originaltitle.setter 

88 def originaltitle(self, originaltitle: str): 

89 if originaltitle is not None: 

90 self.__data['originaltitle'] = originaltitle 

91 

92 @property 

93 def sorttitle(self) -> typing.Optional[str]: 

94 """Get sortable title of movie.""" 

95 return self.__data.get('sorttitle', self.title) 

96 

97 @sorttitle.setter 

98 def sorttitle(self, sorttitle: str): 

99 if sorttitle is not None: 

100 self.__data['sorttitle'] = sorttitle 

101 

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'] 

106 

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 } 

119 

120 @property 

121 def plot(self) -> typing.Optional[str]: 

122 """Get movie plot.""" 

123 return self.__data.get('plot', None) 

124 

125 @plot.setter 

126 def plot(self, plot): 

127 if plot is not None: 

128 self.__data['plot'] = plot 

129 

130 @property 

131 def tagline(self): 

132 """Get movie tagline.""" 

133 return self.__data.get('tagline', None) 

134 

135 @tagline.setter 

136 def tagline(self, tagline): 

137 if tagline is not None: 

138 self.__data['tagline'] = tagline 

139 

140 @property 

141 def runtime(self) -> typing.Optional[int]: 

142 """Get runtime in minutes.""" 

143 return self.__data.get('runtime', None) 

144 

145 @runtime.setter 

146 def runtime(self, minutes: typing.Optional[int]): 

147 if minutes is not None: 

148 self.__data['runtime'] = minutes 

149 

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) 

154 

155 @poster.setter 

156 def poster(self, poster: typing.Optional[str]): 

157 if poster is None: 

158 return 

159 

160 if 'thumb' not in self.__data: 

161 self.__data['thumb'] = {} 

162 

163 self.__data['thumb']['poster'] = poster 

164 

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) 

169 

170 def add_uniqueid(self, idtype: str, value: str): 

171 """Add a new uniqueid in movie.""" 

172 if value is None: 

173 return 

174 

175 if 'uniqueid' not in self.__data: 

176 self.__data['uniqueid'] = {} 

177 

178 self.__data['uniqueid'][idtype] = value 

179 

180 @property 

181 def genre(self) -> typing.Iterable[str]: 

182 """Get list of genres.""" 

183 return self.__data.get('genre', {}) 

184 

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) 

192 

193 @property 

194 def country(self) -> typing.Iterable[str]: 

195 """Get list of production countries.""" 

196 return self.__data.get('country', {}) 

197 

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) 

205 

206 @property 

207 def writter(self) -> typing.Iterable[str]: 

208 """Get list of writter.""" 

209 return self.__data.get('credits', {}) 

210 

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) 

218 

219 @property 

220 def director(self) -> typing.Iterable[str]: 

221 """Get list of director.""" 

222 return self.__data.get('director', {}) 

223 

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) 

231 

232 @property 

233 def premiered(self) -> typing.Optional[str]: 

234 """Get premierered date.""" 

235 return self.__data.get('premiered', None) 

236 

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 

241 

242 @property 

243 def studio(self) -> typing.Sequence[str]: 

244 """Get production studios.""" 

245 return self.__data.get('studio', {}) 

246 

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} 

254 

255 @property 

256 def actors(self) -> typing.Sequence[ActorType]: 

257 """Get actors.""" 

258 return self.__data.get('actor', []) 

259 

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'] = [] 

264 

265 self.__data['actor'].append({ 

266 'name': name, 

267 'role': role, 

268 'order': order, 

269 'thumb': thumb 

270 }) 

271 

272 @property 

273 def technical(self): 

274 """Retrieve technical information on movie file.""" 

275 return self.__data.get('fileinfo', {}) 

276 

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 } 

286 

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 

310 

311 filehandle.write(etree.tounicode(root, pretty_print=True)) 

312 

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']) 

328 

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 

334 

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 

344 

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]) 

352 

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])