8c1d841217d786f23ae27af9abcd8fe9e7b62c74
[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, pin, 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         pin : bool
239             Needs adult pin
240
241         build_url : :obj:`fn`
242             Function to generate the stream url
243         """
244
245         movie_meta = '%s (%d)' % (title, year)
246         folder = alt_title
247         dirname = os.path.join(self.movie_path, folder)
248         filename = os.path.join(dirname, movie_meta + '.strm')
249         if os.path.exists(filename):
250             return
251         if not os.path.exists(dirname):
252             os.makedirs(dirname)
253         if self.movie_exists(title=title, year=year) == False:
254             self.db[self.movies_label][movie_meta] = {'alt_title': alt_title}
255             self._update_local_db(filename=self.db_filepath, db=self.db)
256         self.write_strm_file(path=filename, url=build_url({'action': 'play_video', 'video_id': video_id, 'pin': pin}))
257
258     def add_show (self, title, alt_title, episodes, build_url):
259         """Adds a show to the local db, generates & persists the strm files
260
261         Note: Can also used to store complete seasons or single episodes, it all depends on
262         what is present in the episodes dictionary
263
264         Parameters
265         ----------
266         title : :obj:`str`
267             Title of the show
268
269         alt_title : :obj:`str`
270             Alternative title given by the user
271
272         episodes : :obj:`dict` of :obj:`dict`
273             Episodes that need to be added
274
275         build_url : :obj:`fn`
276             Function to generate the stream url
277         """
278         show_meta = '%s' % (title)
279         folder = alt_title
280         show_dir = os.path.join(self.tvshow_path, folder)
281         if not os.path.exists(show_dir):
282             os.makedirs(show_dir)
283         if self.show_exists(title) == False:
284             self.db[self.series_label][show_meta] = {'seasons': [], 'episodes': [], 'alt_title': alt_title}
285         for episode in episodes:
286             self._add_episode(show_dir=show_dir, title=title, season=episode['season'], episode=episode['episode'], video_id=episode['id'], pin=episode['pin'], build_url=build_url)
287         self._update_local_db(filename=self.db_filepath, db=self.db)
288         return show_dir
289
290     def _add_episode (self, title, show_dir, season, episode, video_id, pin, build_url):
291         """Adds a single episode to the local DB, generates & persists the strm file
292
293         Parameters
294         ----------
295         title : :obj:`str`
296             Title of the show
297
298         show_dir : :obj:`str`
299             Directory that holds the stream files for that show
300
301         season : :obj:`int`
302             Season sequence number
303
304         episode : :obj:`int`
305             Episode sequence number
306
307         video_id : :obj:`str`
308             ID of the video to be played
309
310         pin : bool
311             Needs adult pin
312
313         build_url : :obj:`fn`
314             Function to generate the stream url
315         """
316         season = int(season)
317         episode = int(episode)
318
319         # add season
320         if self.season_exists(title=title, season=season) == False:
321             self.db[self.series_label][title]['seasons'].append(season)
322
323         # add episode
324         episode_meta = 'S%02dE%02d' % (season, episode)
325         if self.episode_exists(title=title, season=season, episode=episode) == False:
326             self.db[self.series_label][title]['episodes'].append(episode_meta)
327
328         # create strm file
329         filename = episode_meta + '.strm'
330         filepath = os.path.join(show_dir, filename)
331         if os.path.exists(filepath):
332             return
333         self.write_strm_file(path=filepath, url=build_url({'action': 'play_video', 'video_id': video_id, 'pin': pin}))
334
335     def remove_movie (self, title, year):
336         """Removes the DB entry & the strm file for the movie given
337
338         Parameters
339         ----------
340         title : :obj:`str`
341             Title of the movie
342
343         year : :obj:`int`
344             Release year of the movie
345
346         Returns
347         -------
348         bool
349             Delete successfull
350         """
351         movie_meta = '%s (%d)' % (title, year)
352         folder = self.db[self.movies_label][movie_meta]['alt_title']
353         del self.db[self.movies_label][movie_meta]
354         self._update_local_db(filename=self.db_filepath, db=self.db)
355         dirname = os.path.join(self.movie_path, folder)
356         if os.path.exists(dirname):
357             shutil.rmtree(dirname)
358             return True
359         return False
360
361     def remove_show (self, title):
362         """Removes the DB entry & the strm files for the show given
363
364         Parameters
365         ----------
366         title : :obj:`str`
367             Title of the show
368
369         Returns
370         -------
371         bool
372             Delete successfull
373         """
374         folder = self.db[self.series_label][title]['alt_title']
375         del self.db[self.series_label][title]
376         self._update_local_db(filename=self.db_filepath, db=self.db)
377         show_dir = os.path.join(self.tvshow_path, folder)
378         if os.path.exists(show_dir):
379             shutil.rmtree(show_dir)
380             return True
381         return False
382
383     def remove_season (self, title, season):
384         """Removes the DB entry & the strm files for a season of a show given
385
386         Parameters
387         ----------
388         title : :obj:`str`
389             Title of the show
390
391         season : :obj:`int`
392             Season sequence number
393
394         Returns
395         -------
396         bool
397             Delete successfull
398         """
399         season = int(season)
400         season_list = []
401         episodes_list = []
402         show_meta = '%s' % (title)
403         for season_entry in self.db[self.series_label][show_meta]['seasons']:
404             if season_entry != season:
405                 season_list.append(season_entry)
406         self.db[self.series_label][show_meta]['seasons'] = season_list
407         show_dir = os.path.join(self.tvshow_path, self.db[self.series_label][show_meta]['alt_title'])
408         if os.path.exists(show_dir):
409             show_files = [f for f in os.listdir(show_dir) if os.path.isfile(os.path.join(show_dir, f))]
410             for filename in show_files:
411                 if 'S%02dE' % (season) in filename:
412                     os.remove(os.path.join(show_dir, filename))
413                 else:
414                     episodes_list.append(filename.replace('.strm', ''))
415             self.db[self.series_label][show_meta]['episodes'] = episodes_list
416         self._update_local_db(filename=self.db_filepath, db=self.db)
417         return True
418
419     def remove_episode (self, title, season, episode):
420         """Removes the DB entry & the strm files for an episode of a show given
421
422         Parameters
423         ----------
424         title : :obj:`str`
425             Title of the show
426
427         season : :obj:`int`
428             Season sequence number
429
430         episode : :obj:`int`
431             Episode sequence number
432
433         Returns
434         -------
435         bool
436             Delete successfull
437         """
438         episodes_list = []
439         show_meta = '%s' % (title)
440         episode_meta = 'S%02dE%02d' % (season, episode)
441         show_dir = os.path.join(self.tvshow_path, self.db[self.series_label][show_meta]['alt_title'])
442         if os.path.exists(os.path.join(show_dir, episode_meta + '.strm')):
443             os.remove(os.path.join(show_dir, episode_meta + '.strm'))
444         for episode_entry in self.db[self.series_label][show_meta]['episodes']:
445             if episode_meta != episode_entry:
446                 episodes_list.append(episode_entry)
447         self.db[self.series_label][show_meta]['episodes'] = episodes_list
448         self._update_local_db(filename=self.db_filepath, db=self.db)
449         return True