__init__.py 32 KB
Newer Older
1
# Author: echel0n <[email protected]>
echel0n's avatar
echel0n committed
2
# URL: https://sickrage.ca
3
#
echel0n's avatar
echel0n committed
4
# This file is part of SiCKRAGE.
5
#
echel0n's avatar
echel0n committed
6
# SiCKRAGE is free software: you can redistribute it and/or modify
7
8
9
10
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
echel0n's avatar
echel0n committed
11
# SiCKRAGE is distributed in the hope that it will be useful,
12
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#  GNU General Public License for more details.
15
16
#
# You should have received a copy of the GNU General Public License
echel0n's avatar
echel0n committed
17
# along with SiCKRAGE.  If not, see <http://www.gnu.org/licenses/>.
18

19

20
import importlib
21
import inspect
22
import os
23
import pkgutil
24
import re
25
from xml.etree.ElementTree import ElementTree
26

27
import fanart
28

29
import sickrage
echel0n's avatar
echel0n committed
30
from sickrage.core.helpers import chmod_as_parent, replace_extension, try_int
31
from sickrage.core.websession import WebSession
32
from sickrage.indexers import IndexerApi
33
from sickrage.indexers.exceptions import indexer_episodenotfound, indexer_seasonnotfound
34
from sickrage.indexers.helpers import map_indexers
35
36


37
class GenericMetadata(object):
38
39
    """
    Base class for all metadata providers. Default behavior is meant to mostly
echel0n's avatar
echel0n committed
40
    follow KODI 12+ metadata standards. Has support for:
41
42
43
44
45
46
47
48
49
50
51
52
    - show metadata file
    - episode metadata file
    - episode thumbnail
    - show fanart
    - show poster
    - show banner
    - season thumbnails (poster)
    - season thumbnails (banner)
    - season all poster
    - season all banner
    """

53
54
55
56
57
58
59
60
61
62
63
64
    def __init__(self,
                 show_metadata=False,
                 episode_metadata=False,
                 fanart=False,
                 poster=False,
                 banner=False,
                 episode_thumbnails=False,
                 season_posters=False,
                 season_banners=False,
                 season_all_poster=False,
                 season_all_banner=False,
                 enabled=False):
65

66
        self.name = "Generic"
67
        self.enabled = enabled
68

69
70
        self._ep_nfo_extension = "nfo"
        self._show_metadata_filename = "tvshow.nfo"
71

72
73
74
        self.fanart_name = "fanart.jpg"
        self.poster_name = "poster.jpg"
        self.banner_name = "banner.jpg"
75

76
77
        self.season_all_poster_name = "season-all-poster.jpg"
        self.season_all_banner_name = "season-all-banner.jpg"
78
79
80
81
82
83
84
85
86
87
88
89

        self.show_metadata = show_metadata
        self.episode_metadata = episode_metadata
        self.fanart = fanart
        self.poster = poster
        self.banner = banner
        self.episode_thumbnails = episode_thumbnails
        self.season_posters = season_posters
        self.season_banners = season_banners
        self.season_all_poster = season_all_poster
        self.season_all_banner = season_all_banner

90
91
92
    @property
    def id(self):
        return str(re.sub(r"[^\w\d_]", "_", str(re.sub(r"[+]", "plus", self.name))).lower())
93

94
95
96
97
98
99
100
101
102
103
104
    @property
    def config(self):
        return "|".join(map(str, map(int, [self.show_metadata, self.episode_metadata, self.fanart, self.poster, self.banner, self.episode_thumbnails,
                                           self.season_posters, self.season_banners, self.season_all_poster, self.season_all_banner, self.enabled])))

    @config.setter
    def config(self, value):
        if not value:
            value = '0|0|0|0|0|0|0|0|0|0|0'

        self.show_metadata, self.episode_metadata, self.fanart, self.poster, self.banner, self.episode_thumbnails, self.season_posters, \
105
        self.season_banners, self.season_all_poster, self.season_all_banner, self.enabled = tuple(map(bool, map(int, value.split('|'))))
106

107
108
109
    @staticmethod
    def _check_exists(location):
        if location:
110
            result = os.path.isfile(location)
111
112
113
            return result
        return False

114
    def _has_show_metadata(self, show_obj):
115
        return self._check_exists(self.get_show_file_path(show_obj))
116
117

    def _has_episode_metadata(self, ep_obj):
118
        return self._check_exists(self.get_episode_file_path(ep_obj))
119
120

    def _has_fanart(self, show_obj):
121
        return self._check_exists(self.get_fanart_path(show_obj))
122
123

    def _has_poster(self, show_obj):
124
        return self._check_exists(self.get_poster_path(show_obj))
125
126

    def _has_banner(self, show_obj):
127
        return self._check_exists(self.get_banner_path(show_obj))
128
129

    def _has_episode_thumb(self, ep_obj):
130
        return self._check_exists(self.get_episode_thumb_path(ep_obj))
131
132

    def _has_season_poster(self, show_obj, season):
133
        return self._check_exists(self.get_season_poster_path(show_obj, season))
134
135

    def _has_season_banner(self, show_obj, season):
136
        return self._check_exists(self.get_season_banner_path(show_obj, season))
137
138

    def _has_season_all_poster(self, show_obj):
139
        return self._check_exists(self.get_season_all_poster_path(show_obj))
140
141

    def _has_season_all_banner(self, show_obj):
142
        return self._check_exists(self.get_season_all_banner_path(show_obj))
143
144

    def get_show_file_path(self, show_obj):
145
        return os.path.join(show_obj.location, self._show_metadata_filename)
146
147

    def get_episode_file_path(self, ep_obj):
echel0n's avatar
echel0n committed
148
        return replace_extension(ep_obj.location, self._ep_nfo_extension)
149
150

    def get_fanart_path(self, show_obj):
151
        return os.path.join(show_obj.location, self.fanart_name)
152
153

    def get_poster_path(self, show_obj):
154
        return os.path.join(show_obj.location, self.poster_name)
155
156

    def get_banner_path(self, show_obj):
157
        return os.path.join(show_obj.location, self.banner_name)
158

159
160
    @staticmethod
    def get_episode_thumb_path(ep_obj):
161
162
163
164
        """
        Returns the path where the episode thumbnail should be stored.
        ep_obj: a TVEpisode instance for which to create the thumbnail
        """
165
        if os.path.isfile(ep_obj.location):
166
167
168
169
170
171
172
173
174
175
176
177

            tbn_filename = ep_obj.location.rpartition(".")

            if tbn_filename[0] == "":
                tbn_filename = ep_obj.location + "-thumb.jpg"
            else:
                tbn_filename = tbn_filename[0] + "-thumb.jpg"
        else:
            return None

        return tbn_filename

178
179
    @staticmethod
    def get_season_poster_path(show_obj, season):
180
181
182
183
184
185
186
187
188
189
190
191
192
193
        """
        Returns the full path to the file for a given season poster.

        show_obj: a TVShow instance for which to generate the path
        season: a season number to be used for the path. Note that season 0
                means specials.
        """

        # Our specials thumbnail is, well, special
        if season == 0:
            season_poster_filename = 'season-specials'
        else:
            season_poster_filename = 'season' + str(season).zfill(2)

194
        return os.path.join(show_obj.location, season_poster_filename + '-poster.jpg')
195

196
197
    @staticmethod
    def get_season_banner_path(show_obj, season):
198
199
200
201
202
203
204
205
206
207
208
209
210
211
        """
        Returns the full path to the file for a given season banner.

        show_obj: a TVShow instance for which to generate the path
        season: a season number to be used for the path. Note that season 0
                means specials.
        """

        # Our specials thumbnail is, well, special
        if season == 0:
            season_banner_filename = 'season-specials'
        else:
            season_banner_filename = 'season' + str(season).zfill(2)

212
        return os.path.join(show_obj.location, season_banner_filename + '-banner.jpg')
213
214

    def get_season_all_poster_path(self, show_obj):
215
        return os.path.join(show_obj.location, self.season_all_poster_name)
216
217

    def get_season_all_banner_path(self, show_obj):
218
        return os.path.join(show_obj.location, self.season_all_banner_name)
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233

    def _show_data(self, show_obj):
        """
        This should be overridden by the implementing class. It should
        provide the content of the show metadata file.
        """
        return None

    def _ep_data(self, ep_obj):
        """
        This should be overridden by the implementing class. It should
        provide the content of the episode metadata file.
        """
        return None

234
235
    def create_show_metadata(self, show_obj, force=False):
        if self.show_metadata and show_obj and (not self._has_show_metadata(show_obj) or force):
236
            sickrage.app.log.debug("Metadata provider " + self.name + " creating show metadata for " + show_obj.name)
237
238
239
            return self.write_show_file(show_obj)
        return False

240
241
    def create_episode_metadata(self, ep_obj, force=False):
        if self.episode_metadata and ep_obj and (not self._has_episode_metadata(ep_obj) or force):
242
            sickrage.app.log.debug("Metadata provider " + self.name + " creating episode metadata for " + ep_obj.pretty_name())
243
244
245
            return self.write_ep_file(ep_obj)
        return False

246
247
    def create_fanart(self, show_obj, which=0, force=False):
        if self.fanart and show_obj and (not self._has_fanart(show_obj) or force):
248
            sickrage.app.log.debug("Metadata provider " + self.name + " creating fanart for " + show_obj.name)
249
            return self.save_fanart(show_obj, which)
250
251
        return False

252
253
    def create_poster(self, show_obj, which=0, force=False):
        if self.poster and show_obj and (not self._has_poster(show_obj) or force):
254
            sickrage.app.log.debug("Metadata provider " + self.name + " creating poster for " + show_obj.name)
255
            return self.save_poster(show_obj, which)
256
257
        return False

258
259
    def create_banner(self, show_obj, which=0, force=False):
        if self.banner and show_obj and (not self._has_banner(show_obj) or force):
260
            sickrage.app.log.debug("Metadata provider " + self.name + " creating banner for " + show_obj.name)
261
            return self.save_banner(show_obj, which)
262
263
        return False

264
265
    def create_episode_thumb(self, ep_obj, force=False):
        if self.episode_thumbnails and ep_obj and (not self._has_episode_thumb(ep_obj) or force):
266
            sickrage.app.log.debug("Metadata provider " + self.name + " creating episode thumbnail for " + ep_obj.pretty_name())
267
268
269
            return self.save_thumbnail(ep_obj)
        return False

270
    def create_season_posters(self, show_obj, force=False):
271
272
        if self.season_posters and show_obj:
            result = []
273
            for ep_obj in show_obj.episodes:
274
                if not self._has_season_poster(show_obj, ep_obj.season) or force:
275
                    sickrage.app.log.debug("Metadata provider " + self.name + " creating season posters for " + show_obj.name)
276
                    result = result + [self.save_season_poster(show_obj, ep_obj.season)]
277
278
279
            return all(result)
        return False

280
    def create_season_banners(self, show_obj, force=False):
281
282
        if self.season_banners and show_obj:
            result = []
283
            sickrage.app.log.debug("Metadata provider " + self.name + " creating season banners for " + show_obj.name)
284
            for ep_obj in show_obj.episodes:
285
286
                if not self._has_season_banner(show_obj, ep_obj.season) or force:
                    result = result + [self.save_season_banner(show_obj, ep_obj.season)]
287
288
289
            return all(result)
        return False

290
291
    def create_season_all_poster(self, show_obj, force=False):
        if self.season_all_poster and show_obj and (not self._has_season_all_poster(show_obj) or force):
292
            sickrage.app.log.debug("Metadata provider " + self.name + " creating season all poster for " + show_obj.name)
293
294
295
            return self.save_season_all_poster(show_obj)
        return False

296
297
    def create_season_all_banner(self, show_obj, force=False):
        if self.season_all_banner and show_obj and (not self._has_season_all_banner(show_obj) or force):
298
            sickrage.app.log.debug("Metadata provider " + self.name + " creating season all banner for " + show_obj.name)
299
300
301
302
303
304
            return self.save_season_all_banner(show_obj)
        return False

    def _get_episode_thumb_url(self, ep_obj):
        """
        Returns the URL to use for downloading an episode's thumbnail. Uses
305
        theTVDB.com data.
306
307
308

        ep_obj: a TVEpisode object for which to grab the thumb URL
        """
309
        all_eps = [ep_obj] + ep_obj.related_episodes
310

311
        # validate show
312
        if not self.validateShow(ep_obj.show):
313
314
315
316
            return None

        # try all included episodes in case some have thumbs and others don't
        for cur_ep in all_eps:
317
            myEp = self.validateShow(cur_ep.show, cur_ep.season, cur_ep.episode)
318
            if not myEp:
319
320
321
                continue

            thumb_url = getattr(myEp, 'filename', None)
322
            if thumb_url:
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
                return thumb_url

        return None

    def write_show_file(self, show_obj):
        """
        Generates and writes show_obj's metadata under the given path to the
        filename given by get_show_file_path()

        show_obj: TVShow object for which to create the metadata

        path: An absolute or relative path where we should put the file. Note that
                the file name will be the default show_file_name.

        Note that this method expects that _show_data will return an ElementTree
338
        object. If your _show_data returns data in another format yo'll need to
339
340
341
342
343
344
345
346
        override this method.
        """

        data = self._show_data(show_obj)
        if not data:
            return False

        nfo_file_path = self.get_show_file_path(show_obj)
347
        nfo_file_dir = os.path.dirname(nfo_file_path)
348
349

        try:
350
            if not os.path.isdir(nfo_file_dir):
351
                sickrage.app.log.debug("Metadata dir didn't exist, creating it at " + nfo_file_dir)
352
                os.makedirs(nfo_file_dir)
353
                chmod_as_parent(nfo_file_dir)
354

355
            sickrage.app.log.debug("Writing show nfo file to " + nfo_file_path)
356

357
            with open(nfo_file_path, 'wb') as nfo_file:
358
359
                data.write(nfo_file, encoding='utf-8')

360
            chmod_as_parent(nfo_file_path)
361
        except IOError as e:
362
            sickrage.app.log.warning("Unable to write file to " + nfo_file_path + " - are you sure the folder is writable? {}".format(e))
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
            return False

        return True

    def write_ep_file(self, ep_obj):
        """
        Generates and writes ep_obj's metadata under the given path with the
        given filename root. Uses the episode's name with the extension in
        _ep_nfo_extension.

        ep_obj: TVEpisode object for which to create the metadata

        file_name_path: The file name to use for this metadata. Note that the extension
                will be automatically added based on _ep_nfo_extension. This should
                include an absolute path.

        Note that this method expects that _ep_data will return an ElementTree
380
        object. If your _ep_data returns data in another format yo'll need to
381
382
383
384
385
386
387
388
        override this method.
        """

        data = self._ep_data(ep_obj)
        if not data:
            return False

        nfo_file_path = self.get_episode_file_path(ep_obj)
389
        nfo_file_dir = os.path.dirname(nfo_file_path)
390
391

        try:
392
            if not os.path.isdir(nfo_file_dir):
393
                sickrage.app.log.debug("Metadata dir didn't exist, creating it at " + nfo_file_dir)
394
                os.makedirs(nfo_file_dir)
395
                chmod_as_parent(nfo_file_dir)
396

397
            sickrage.app.log.debug("Writing episode nfo file to " + nfo_file_path)
398

399
            with open(nfo_file_path, 'wb') as nfo_file:
400
401
                data.write(nfo_file, encoding='utf-8')

402
            chmod_as_parent(nfo_file_path)
403
        except IOError as e:
404
            sickrage.app.log.warning("Unable to write file to " + nfo_file_path + " - are you sure the folder is writable? {}".format(e))
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
            return False

        return True

    def save_thumbnail(self, ep_obj):
        """
        Retrieves a thumbnail and saves it to the correct spot. This method should not need to
        be overridden by implementing classes, changing get_episode_thumb_path and
        _get_episode_thumb_url should suffice.

        ep_obj: a TVEpisode object for which to generate a thumbnail
        """

        file_path = self.get_episode_thumb_path(ep_obj)

        if not file_path:
421
            sickrage.app.log.debug("Unable to find a file path to use for this thumbnail, not generating it")
422
423
424
425
426
427
            return False

        thumb_url = self._get_episode_thumb_url(ep_obj)

        # if we can't find one then give up
        if not thumb_url:
428
            sickrage.app.log.debug("No thumb is available for this episode, not creating a thumb")
429
430
            return False

431
        thumb_data = self.get_show_image(thumb_url)
432

433
        result = self._write_image(thumb_data, file_path)
434
435
436
437

        if not result:
            return False

438
        for cur_ep in [ep_obj] + ep_obj.related_episodes:
439
            cur_ep.hastbn = True
440
            cur_ep.save()
441
442
443

        return True

444
    def save_fanart(self, show_obj, which=0):
445
446
447
448
449
450
451
452
453
454
455
456
457
        """
        Downloads a fanart image and saves it to the filename specified by fanart_name
        inside the show's root folder.

        show_obj: a TVShow object for which to download fanart
        """

        # use the default fanart name
        fanart_path = self.get_fanart_path(show_obj)

        fanart_data = self._retrieve_show_image('fanart', show_obj, which)

        if not fanart_data:
458
            sickrage.app.log.debug("No fanart image was retrieved, unable to write fanart")
459
460
            return False

461
        return self._write_image(fanart_data, fanart_path)
462

463
    def save_poster(self, show_obj, which=0):
464
465
466
467
468
469
470
471
472
473
474
475
476
        """
        Downloads a poster image and saves it to the filename specified by poster_name
        inside the show's root folder.

        show_obj: a TVShow object for which to download a poster
        """

        # use the default poster name
        poster_path = self.get_poster_path(show_obj)

        poster_data = self._retrieve_show_image('poster', show_obj, which)

        if not poster_data:
477
            sickrage.app.log.debug("No show poster image was retrieved, unable to write poster")
478
479
            return False

480
        return self._write_image(poster_data, poster_path)
481

482
    def save_banner(self, show_obj, which=0):
483
484
485
486
487
488
489
490
491
492
        """
        Downloads a banner image and saves it to the filename specified by banner_name
        inside the show's root folder.

        show_obj: a TVShow object for which to download a banner
        """

        # use the default banner name
        banner_path = self.get_banner_path(show_obj)

echel0n's avatar
echel0n committed
493
        banner_data = self._retrieve_show_image('series', show_obj, which)
494
495

        if not banner_data:
496
            sickrage.app.log.debug("No show banner image was retrieved, unable to write banner")
497
498
            return False

499
        return self._write_image(banner_data, banner_path)
500

501
502
    def save_season_poster(self, show_obj, season, which=0):
        season_url = self._retrieve_season_poster_image(show_obj, season, which)
503

echel0n's avatar
echel0n committed
504
505
        season_poster_file_path = self.get_season_poster_path(show_obj, season)
        if not season_poster_file_path:
506
            sickrage.app.log.debug("Path for season " + str(season) + " came back blank, skipping this season")
507
508
            return False

509
        seasonData = self.get_show_image(season_url)
echel0n's avatar
echel0n committed
510
        if not seasonData:
511
            sickrage.app.log.debug("No season poster data available, skipping this season")
echel0n's avatar
echel0n committed
512
            return False
513

echel0n's avatar
echel0n committed
514
        return self._write_image(seasonData, season_poster_file_path)
515

516
517
    def save_season_banner(self, show_obj, season, which=0):
        season_url = self._retrieve_season_banner_image(show_obj, season, which)
518

echel0n's avatar
echel0n committed
519
520
        season_banner_file_path = self.get_season_banner_path(show_obj, season)
        if not season_banner_file_path:
521
            sickrage.app.log.debug("Path for season " + str(season) + " came back blank, skipping this season")
echel0n's avatar
echel0n committed
522
            return False
523

524
        seasonData = self.get_show_image(season_url)
echel0n's avatar
echel0n committed
525
        if not seasonData:
526
            sickrage.app.log.debug("No season banner data available, skipping this season")
527
528
            return False

echel0n's avatar
echel0n committed
529
530
        return self._write_image(seasonData, season_banner_file_path)

531
    def save_season_all_poster(self, show_obj, which=0):
532
533
534
535
536
537
        # use the default season all poster name
        poster_path = self.get_season_all_poster_path(show_obj)

        poster_data = self._retrieve_show_image('poster', show_obj, which)

        if not poster_data:
538
            sickrage.app.log.debug("No show poster image was retrieved, unable to write season all poster")
539
540
            return False

541
        return self._write_image(poster_data, poster_path)
542

543
    def save_season_all_banner(self, show_obj, which=0):
544
545
546
        # use the default season all banner name
        banner_path = self.get_season_all_banner_path(show_obj)

echel0n's avatar
echel0n committed
547
        banner_data = self._retrieve_show_image('series', show_obj, which)
548
549

        if not banner_data:
550
            sickrage.app.log.debug("No show banner image was retrieved, unable to write season all banner")
551
552
            return False

553
        return self._write_image(banner_data, banner_path)
554

555
    def _write_image(self, image_data, image_path, force=False):
556
557
558
559
560
561
562
563
564
        """
        Saves the data in image_data to the location image_path. Returns True/False
        to represent success or failure.

        image_data: binary image data to write to file
        image_path: file location to save the image to
        """

        # don't bother overwriting it
565
        if os.path.isfile(image_path) and not force:
566
            sickrage.app.log.debug("Image already exists, not downloading")
567
568
            return False

569
        image_dir = os.path.dirname(image_path)
570

571
        if not image_data:
572
            sickrage.app.log.debug("Unable to retrieve image to save in %s, skipping" % image_path)
573
574
575
            return False

        try:
576
            if not os.path.isdir(image_dir):
577
                sickrage.app.log.debug("Metadata dir didn't exist, creating it at " + image_dir)
578
                os.makedirs(image_dir)
579
                chmod_as_parent(image_dir)
580

581
            with open(image_path, 'wb') as outFile:
582
583
                outFile.write(image_data)

584
            chmod_as_parent(image_path)
585
        except IOError as e:
586
            sickrage.app.log.warning("Unable to write image to " + image_path + " - are you sure the show folder is writable? {}".format(e))
587
588
589
590
            return False

        return True

591
    def _retrieve_show_image(self, image_type, show_obj, which=0):
592
        """
593
        Gets an image URL from theTVDB.com and fanart.tv, downloads it and returns the data.
594
595
596
597
598
599
600

        image_type: type of image to retrieve (currently supported: fanart, poster, banner)
        show_obj: a TVShow object to use when searching for the image
        which: optional, a specific numbered poster to look for

        Returns: the binary image data if available, or else None
        """
601

echel0n's avatar
echel0n committed
602
        image_data = None
603
        indexer_lang = show_obj.lang or sickrage.app.config.indexer_default_language
604

605
606
607
608
        if image_type not in ('fanart', 'poster', 'series', 'poster_thumb', 'series_thumb', 'fanart_thumb'):
            sickrage.app.log.warning("Invalid image type " + str(image_type) + ", couldn't find it in the " + IndexerApi(show_obj.indexer).name + " object")
            return

609
610
611
        # There's gotta be a better way of doing this but we don't wanna
        # change the language value elsewhere
        lINDEXER_API_PARMS = IndexerApi(show_obj.indexer).api_params.copy()
612

613
        lINDEXER_API_PARMS['language'] = indexer_lang
614

615
616
        if show_obj.dvdorder != 0:
            lINDEXER_API_PARMS['dvdorder'] = True
617

618
        t = IndexerApi(show_obj.indexer).indexer(**lINDEXER_API_PARMS)
619

620
        is_image_thumb = '_thumb' in image_type
621
622
623
624
        image_types = {
            '{}'.format(image_type): {
                'indexer': lambda: t.images(show_obj.indexer_id, key_type=image_type.replace('_thumb', ''))[which][('filename', 'thumbnail')[is_image_thumb]],
                'fanart': lambda: self._retrieve_show_images_from_fanart(show_obj, image_type.replace('_thumb', ''), is_image_thumb)
625
            }
626
        }
627

628
        for fname in ['indexer', 'fanart']:
echel0n's avatar
echel0n committed
629
            try:
630
                image_url = image_types[image_type][fname]()
631
632
633
634
                if image_url:
                    image_data = self.get_show_image(image_url)
                    if image_data:
                        break
635
            except (KeyError, IndexError, TypeError) as e:
636
                pass
637

echel0n's avatar
echel0n committed
638
        return image_data
639

640
    @staticmethod
641
    def _retrieve_season_poster_image(show_obj, season, which=0):
642
643
644
645
646
647
648
        """
        Should return a dict like:

        result = {<season number>:
                    {1: '<url 1>', 2: <url 2>, ...},}
        """

649
        indexer_lang = show_obj.lang or sickrage.app.config.indexer_default_language
650

651
652
653
        try:
            # There's gotta be a better way of doing this but we don't wanna
            # change the language value elsewhere
654
            lINDEXER_API_PARMS = IndexerApi(show_obj.indexer).api_params.copy()
655

656
            lINDEXER_API_PARMS['language'] = indexer_lang
657
658

            if show_obj.dvdorder != 0:
659
                lINDEXER_API_PARMS['dvdorder'] = True
660

661
            t = IndexerApi(show_obj.indexer).indexer(**lINDEXER_API_PARMS)
echel0n's avatar
echel0n committed
662
663

            # Give us just the normal poster-style season graphics
664
665
666
667
            image_data = t.images(show_obj.indexer_id, key_type='season', season=season)
            if image_data:
                return image_data[which]['filename']

668
            sickrage.app.log.warning("{}: Unable to look up show on ".format(show_obj.indexer_id) + IndexerApi(
669
                show_obj.indexer).name + ", not downloading images")
670
671
        except (KeyError, IndexError):
            pass
672

673
    @staticmethod
674
    def _retrieve_season_banner_image(show_obj, season, which=0):
675
676
677
678
679
680
681
        """
        Should return a dict like:

        result = {<season number>:
                    {1: '<url 1>', 2: <url 2>, ...},}
        """

682
        indexer_lang = show_obj.lang or sickrage.app.config.indexer_default_language
683
684
685
686

        try:
            # There's gotta be a better way of doing this but we don't wanna
            # change the language value elsewhere
687
            lINDEXER_API_PARMS = IndexerApi(show_obj.indexer).api_params.copy()
688

689
            lINDEXER_API_PARMS['language'] = indexer_lang
690

691
            t = IndexerApi(show_obj.indexer).indexer(**lINDEXER_API_PARMS)
echel0n's avatar
echel0n committed
692
693

            # Give us just the normal season graphics
694
695
696
697
            image_data = t.images(show_obj.indexer_id, key_type='seasonwide', season=season)
            if image_data:
                return image_data[which]['filename']

698
            sickrage.app.log.warning("{}: Unable to look up show on ".format(show_obj.indexer_id) + IndexerApi(
699
                show_obj.indexer).name + ", not downloading images")
700
701
        except (KeyError, IndexError):
            pass
702

703
    def retrieve_show_metadata(self, folder) -> (int, str, int):
704
        """
705
706
        :param folder:
        :return:
707
708
709
710
        """

        empty_return = (None, None, None)

711
        metadata_path = os.path.join(folder, self._show_metadata_filename)
712

713
        if not os.path.isdir(folder) or not os.path.isfile(metadata_path):
714
            sickrage.app.log.debug("Can't load the metadata file from " + metadata_path + ", it doesn't exist")
715
716
            return empty_return

717
        try:
718
            sickrage.app.log.debug("Loading show info from sickrage.metadata file in {}".format(folder))
719
720
        except:
            pass
721
722

        try:
723
            with open(metadata_path, 'rb') as xmlFileObj:
724
                showXML = ElementTree(file=xmlFileObj)
725

726
            if showXML.findtext('title') is None or (
727
                    showXML.findtext('tvdbid') is None and showXML.findtext('id') is None):
728
729
730
                sickrage.app.log.info("Invalid info in tvshow.nfo (missing name or id): {} {} {}".format(showXML.findtext('title'),
                                                                                                         showXML.findtext('tvdbid'),
                                                                                                         showXML.findtext('id')))
731
732
733
                return empty_return

            name = showXML.findtext('title')
echel0n's avatar
echel0n committed
734

735
736
            indexer_id_text = showXML.findtext('tvdbid') or showXML.findtext('id')
            if indexer_id_text:
737
                indexer_id = try_int(indexer_id_text, None)
738
                if not indexer_id:
739
                    sickrage.app.log.debug("Invalid Indexer ID (" + str(indexer_id) + "), not using metadata file")
740
741
                    return empty_return
            else:
742
                sickrage.app.log.debug("Empty <id> or <tvdbid> field in NFO, unable to find a ID, not using metadata file")
743
744
                return empty_return

745
            if showXML.findtext('tvdbid') is not None:
746
                indexer_id = int(showXML.findtext('tvdbid'))
747
            elif showXML.findtext('id') is not None:
748
749
                indexer_id = int(showXML.findtext('id'))
            else:
750
                sickrage.app.log.warning("Empty <id> or <tvdbid> field in NFO, unable to find a ID")
751
752
                return empty_return

753
754
755
756
757
            indexer = 1
            epg_url_text = showXML.findtext('episodeguide/url')
            if epg_url_text:
                epg_url = epg_url_text.lower()
                if str(indexer_id) in epg_url and 'tvrage' in epg_url:
758
                    sickrage.app.log.warning("Invalid Indexer ID (" + str(indexer_id) + "), not using metadata file because it has TVRage info")
759
                    return empty_return
760

761
        except Exception as e:
762
            sickrage.app.log.warning("There was an error parsing your existing metadata file: '" + metadata_path + "' error: {}".format(e))
763
764
            return empty_return

labrys's avatar
labrys committed
765
        return indexer_id, name, indexer
766

767
768
    @staticmethod
    def _retrieve_show_images_from_fanart(show, img_type, thumb=False):
769
        types = {
770
771
772
            'poster': fanart.TYPE.TV.POSTER,
            'series': fanart.TYPE.TV.BANNER,
            'fanart': fanart.TYPE.TV.BACKGROUND,
773
        }
774

775
        sickrage.app.log.debug("Searching for any " + img_type + " images on Fanart.tv for " + show.name)
776

777
        try:
778
            indexer_id = map_indexers(show.indexer, show.indexer_id, show.name)[1]
779
            if indexer_id:
780
                request = fanart.Request(
781
                    apikey=sickrage.app.config.fanart_api_key,
782
                    id=indexer_id,
783
                    ws=fanart.WS.TV,
784
                    type=types[img_type],
785
786
                    sort=fanart.SORT.POPULAR,
                    limit=fanart.LIMIT.ONE,
787
788
789
                )

                resp = request.response()
790
                url = resp[types[img_type]][0]['url']
791
792
793
                if thumb:
                    url = re.sub('/fanart/', '/preview/', url)
                return url
echel0n's avatar
echel0n committed
794
        except Exception:
795
            pass
796

797
        sickrage.app.log.debug("Could not find any " + img_type + " images on Fanart.tv for " + show.name)
798
799
800

    @staticmethod
    def validateShow(show, season=None, episode=None):
801
        indexer_lang = show.lang or sickrage.app.config.indexer_default_language
802
803

        try:
804
            lINDEXER_API_PARMS = IndexerApi(show.indexer).api_params.copy()
805

806
            lINDEXER_API_PARMS['language'] = indexer_lang
807
808
809
810

            if show.dvdorder != 0:
                lINDEXER_API_PARMS['dvdorder'] = True

811
            t = IndexerApi(show.indexer).indexer(**lINDEXER_API_PARMS)
812
813
814
            if season is None and episode is None:
                return t

815
            return t[show.indexer_id][season][episode]
816
817
        except (indexer_episodenotfound, indexer_seasonnotfound):
            pass
818

819
820
821
822
823
824
825
826
    @staticmethod
    def get_show_image(url):
        if url is None:
            return None

        sickrage.app.log.debug("Fetching image from " + url)

        try:
827
            return WebSession().get(url, verify=False).content
828
        except Exception:
829
            sickrage.app.log.debug("There was an error trying to retrieve the image, aborting")
830

831
832
833
834

class MetadataProviders(dict):
    def __init__(self):
        super(MetadataProviders, self).__init__()
835
836
837
838
        for (__, name, __) in pkgutil.iter_modules([os.path.dirname(__file__)]):
            imported_module = importlib.import_module('.' + name, package='sickrage.metadata')
            for __, klass in inspect.getmembers(imported_module,
                                                predicate=lambda o: inspect.isclass(o) and issubclass(o, GenericMetadata) and o is not GenericMetadata):
839
                self[klass().id] = klass()
840
                break