chore(meta): Fixes source in addon.xml
[plugin.video.netflix.git] / resources / lib / Library.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # Module: LibraryExporter
4 # Created on: 13.01.2017
5
6 import os
7 import shutil
8 try:
9    import cPickle as pickle
10 except:
11    import pickle
12 from utils import noop
13
14 class Library:
15     """Exports Netflix shows & movies to a local library folder"""
16
17     series_label = 'shows'
18     """str: Label to identify shows"""
19
20     movies_label = 'movies'
21     """str: Label to identify movies"""
22
23     db_filename = 'lib.ndb'
24     """str: (File)Name of the store for the database dump that contains all shows/movies added to the library"""
25
26     def __init__ (self, root_folder, library_settings, log_fn=noop):
27         """Takes the instances & configuration options needed to drive the plugin
28
29         Parameters
30         ----------
31         root_folder : :obj:`str`
32             Cookie location
33
34         library_settings : :obj:`str`
35             User data cache location
36
37         library_db_path : :obj:`str`
38             User data cache location
39
40         log_fn : :obj:`fn`
41              optional log function
42         """
43         self.base_data_path = root_folder
44         self.enable_custom_library_folder = library_settings['enablelibraryfolder']
45         self.custom_library_folder = library_settings['customlibraryfolder']
46         self.db_filepath = os.path.join(self.base_data_path, self.db_filename)
47         self.log = log_fn
48
49         # check for local library folder & set up the paths
50         lib_path = self.base_data_path if self.enable_custom_library_folder != 'true' else self.custom_library_folder
51         self.movie_path = os.path.join(lib_path, self.movies_label)
52         self.tvshow_path = os.path.join(lib_path, self.series_label)
53
54         # check if we need to setup the base folder structure & do so if needed
55         self.setup_local_netflix_library(source={
56             self.movies_label: self.movie_path,
57             self.series_label: self.tvshow_path
58         })
59
60         # load the local db
61         self.db = self._load_local_db(filename=self.db_filepath)
62
63     def setup_local_netflix_library (self, source):
64         """Sets up the basic directories
65
66         Parameters
67         ----------
68         source : :obj:`dict` of :obj:`str`
69             Dicitionary with directories to be created
70         """
71         for label in source:
72             if not os.path.exists(source[label]):
73                 os.makedirs(source[label])
74
75     def write_strm_file(self, path, url):
76         """Writes the stream file that Kodi can use to integrate it into the DB
77
78         Parameters
79         ----------
80         path : :obj:`str`
81             Filepath of the file to be created
82
83         url : :obj:`str`
84             Stream url
85         """
86         with open(path, 'w+') as f:
87             f.write(url)
88             f.close()
89
90     def _load_local_db (self, filename):
91         """Loads the local db file and parses it, creates one if not existent
92
93         Parameters
94         ----------
95         filename : :obj:`str`
96             Filepath of db file
97
98         Returns
99         -------
100         :obj:`dict`
101             Parsed contents of the db file
102         """
103         # if the db doesn't exist, create it
104         if not os.path.isfile(filename):
105             data = {self.movies_label: {}, self.series_label: {}}
106             self.log('Setup local library DB')
107             self._update_local_db(filename=filename, db=data)
108             return data
109
110         with open(filename) as f:
111             data = pickle.load(f)
112             if data:
113                 return data
114             else:
115                 return {}
116
117     def _update_local_db (self, filename, db):
118         """Updates the local db file with new data
119
120         Parameters
121         ----------
122         filename : :obj:`str`
123             Filepath of db file
124
125         db : :obj:`dict`
126             Database contents
127
128         Returns
129         -------
130         bool
131             Update has been successfully executed
132         """
133         if not os.path.isdir(os.path.dirname(filename)):
134             return False
135         with open(filename, 'w') as f:
136             f.truncate()
137             pickle.dump(db, f)
138         return True
139
140     def movie_exists (self, title, year):
141         """Checks if a movie is already present in the local DB
142
143         Parameters
144         ----------
145         title : :obj:`str`
146             Title of the movie
147
148         year : :obj:`int`
149             Release year of the movie
150
151         Returns
152         -------
153         bool
154             Movie exists in DB
155         """
156         movie_meta = '%s (%d)' % (title, year)
157         return movie_meta in self.db[self.movies_label]
158
159     def show_exists (self, title):
160         """Checks if a show is present in the local DB
161
162         Parameters
163         ----------
164         title : :obj:`str`
165             Title of the show
166
167         Returns
168         -------
169         bool
170             Show exists in DB
171         """
172         show_meta = '%s' % (title)
173         return show_meta in self.db[self.series_label]
174
175     def season_exists (self, title, season):
176         """Checks if a season is present in the local DB
177
178         Parameters
179         ----------
180         title : :obj:`str`
181             Title of the show
182
183         season : :obj:`int`
184             Season sequence number
185
186         Returns
187         -------
188         bool
189             Season of show exists in DB
190         """
191         if self.show_exists(title) == False:
192             return False
193         show_entry = self.db[self.series_label][title]
194         return season in show_entry['seasons']
195
196     def episode_exists (self, title, season, episode):
197         """Checks if an episode if a show is present in the local DB
198
199         Parameters
200         ----------
201         title : :obj:`str`
202             Title of the show
203
204         season : :obj:`int`
205             Season sequence number
206
207         episode : :obj:`int`
208             Episode sequence number
209
210         Returns
211         -------
212         bool
213             Episode of show exists in DB
214         """
215         if self.show_exists(title) == False:
216             return False
217         show_entry = self.db[self.series_label][title]
218         episode_entry = 'S%02dE%02d' % (season, episode)
219         return episode_entry in show_entry['episodes']
220
221     def add_movie (self, title, alt_title, year, video_id, build_url):
222         """Adds a movie to the local db, generates & persists the strm file
223
224         Parameters
225         ----------
226         title : :obj:`str`
227             Title of the show
228
229         alt_title : :obj:`str`
230             Alternative title given by the user
231
232         year : :obj:`int`
233             Release year of the show
234
235         video_id : :obj:`str`
236             ID of the video to be played
237
238         build_url : :obj:`fn`
239             Function to generate the stream url
240         """
241
242         movie_meta = '%s (%d)' % (title, year)
243         folder = alt_title
244         dirname = os.path.join(self.movie_path, folder)
245         filename = os.path.join(dirname, movie_meta + '.strm')
246         if os.path.exists(filename):
247             return
248         if not os.path.exists(dirname):
249             os.makedirs(dirname)
250         if self.movie_exists(title=title, year=year) == False:
251             self.db[self.movies_label][movie_meta] = {'alt_title': alt_title}
252             self._update_local_db(filename=self.db_filepath, db=self.db)
253         self.write_strm_file(path=filename, url=build_url({'action': 'play_video', 'video_id': video_id}))
254
255     def add_show (self, title, alt_title, episodes, build_url):
256         """Adds a show to the local db, generates & persists the strm files
257
258         Note: Can also used to store complete seasons or single episodes, it all depends on
259         what is present in the episodes dictionary
260
261         Parameters
262         ----------
263         title : :obj:`str`
264             Title of the show
265
266         alt_title : :obj:`str`
267             Alternative title given by the user
268
269         episodes : :obj:`dict` of :obj:`dict`
270             Episodes that need to be added
271
272         build_url : :obj:`fn`
273             Function to generate the stream url
274         """
275         show_meta = '%s' % (title)
276         folder = alt_title
277         show_dir = os.path.join(self.tvshow_path, folder)
278         if not os.path.exists(show_dir):
279             os.makedirs(show_dir)
280         if self.show_exists(title) == False:
281             self.db[self.series_label][show_meta] = {'seasons': [], 'episodes': [], 'alt_title': alt_title}
282         for episode in episodes:
283             self._add_episode(show_dir=show_dir, title=title, season=episode['season'], episode=episode['episode'], video_id=episode['id'], build_url=build_url)
284         self._update_local_db(filename=self.db_filepath, db=self.db)
285         return show_dir
286
287     def _add_episode (self, title, show_dir, season, episode, video_id, build_url):
288         """Adds a single episode to the local DB, generates & persists the strm file
289
290         Parameters
291         ----------
292         title : :obj:`str`
293             Title of the show
294
295         show_dir : :obj:`str`
296             Directory that holds the stream files for that show
297
298         season : :obj:`int`
299             Season sequence number
300
301         episode : :obj:`int`
302             Episode sequence number
303
304         video_id : :obj:`str`
305             ID of the video to be played
306
307         build_url : :obj:`fn`
308             Function to generate the stream url
309         """
310         season = int(season)
311         episode = int(episode)
312
313         # add season
314         if self.season_exists(title=title, season=season) == False:
315             self.db[self.series_label][title]['seasons'].append(season)
316
317         # add episode
318         episode_meta = 'S%02dE%02d' % (season, episode)
319         if self.episode_exists(title=title, season=season, episode=episode) == False:
320             self.db[self.series_label][title]['episodes'].append(episode_meta)
321
322         # create strm file
323         filename = episode_meta + '.strm'
324         filepath = os.path.join(show_dir, filename)
325         if os.path.exists(filepath):
326             return
327         self.write_strm_file(path=filepath, url=build_url({'action': 'play_video', 'video_id': video_id}))
328
329     def remove_movie (self, title, year):
330         """Removes the DB entry & the strm file for the movie given
331
332         Parameters
333         ----------
334         title : :obj:`str`
335             Title of the movie
336
337         year : :obj:`int`
338             Release year of the movie
339
340         Returns
341         -------
342         bool
343             Delete successfull
344         """
345         movie_meta = '%s (%d)' % (title, year)
346         folder = self.db[self.movies_label][movie_meta]['alt_title']
347         del self.db[self.movies_label][movie_meta]
348         self._update_local_db(filename=self.db_filepath, db=self.db)
349         dirname = os.path.join(self.movie_path, folder)
350         if os.path.exists(dirname):
351             shutil.rmtree(dirname)
352             return True
353         return False
354
355     def remove_show (self, title):
356         """Removes the DB entry & the strm files for the show given
357
358         Parameters
359         ----------
360         title : :obj:`str`
361             Title of the show
362
363         Returns
364         -------
365         bool
366             Delete successfull
367         """
368         folder = self.db[self.series_label][title]['alt_title']
369         del self.db[self.series_label][title]
370         self._update_local_db(filename=self.db_filepath, db=self.db)
371         show_dir = os.path.join(self.tvshow_path, folder)
372         if os.path.exists(show_dir):
373             shutil.rmtree(show_dir)
374             return True
375         return False
376
377     def remove_season (self, title, season):
378         """Removes the DB entry & the strm files for a season of a show given
379
380         Parameters
381         ----------
382         title : :obj:`str`
383             Title of the show
384
385         season : :obj:`int`
386             Season sequence number
387
388         Returns
389         -------
390         bool
391             Delete successfull
392         """
393         season = int(season)
394         season_list = []
395         episodes_list = []
396         show_meta = '%s' % (title)
397         for season_entry in self.db[self.series_label][show_meta]['seasons']:
398             if season_entry != season:
399                 season_list.append(season_entry)
400         self.db[self.series_label][show_meta]['seasons'] = season_list
401         show_dir = os.path.join(self.tvshow_path, self.db[self.series_label][show_meta]['alt_title'])
402         if os.path.exists(show_dir):
403             show_files = [f for f in os.listdir(show_dir) if os.path.isfile(os.path.join(show_dir, f))]
404             for filename in show_files:
405                 if 'S%02dE' % (season) in filename:
406                     os.remove(os.path.join(show_dir, filename))
407                 else:
408                     episodes_list.append(filename.replace('.strm', ''))
409             self.db[self.series_label][show_meta]['episodes'] = episodes_list
410         self._update_local_db(filename=self.db_filepath, db=self.db)
411         return True
412
413     def remove_episode (self, title, season, episode):
414         """Removes the DB entry & the strm files for an episode of a show given
415
416         Parameters
417         ----------
418         title : :obj:`str`
419             Title of the show
420
421         season : :obj:`int`
422             Season sequence number
423
424         episode : :obj:`int`
425             Episode sequence number
426
427         Returns
428         -------
429         bool
430             Delete successfull
431         """
432         episodes_list = []
433         show_meta = '%s' % (title)
434         episode_meta = 'S%02dE%02d' % (season, episode)
435         show_dir = os.path.join(self.tvshow_path, self.db[self.series_label][show_meta]['alt_title'])
436         if os.path.exists(os.path.join(show_dir, episode_meta + '.strm')):
437             os.remove(os.path.join(show_dir, episode_meta + '.strm'))
438         for episode_entry in self.db[self.series_label][show_meta]['episodes']:
439             if episode_meta != episode_entry:
440                 episodes_list.append(episode_entry)
441         self.db[self.series_label][show_meta]['episodes'] = episodes_list
442         self._update_local_db(filename=self.db_filepath, db=self.db)
443         return True