__init__.py 53.8 KB
Newer Older
echel0n's avatar
echel0n committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ##############################################################################
#  Author: echel0n <[email protected]>
#  URL: https://sickrage.ca/
#  Git: https://git.sickrage.ca/SiCKRAGE/sickrage.git
#  -
#  This file is part of SiCKRAGE.
#  -
#  SiCKRAGE is free software: you can redistribute it and/or modify
#  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.
#  -
#  SiCKRAGE is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  -
#  You should have received a copy of the GNU General Public License
#  along with SiCKRAGE.  If not, see <http://www.gnu.org/licenses/>.
# ##############################################################################
21

22
import datetime
23
24
25
import glob
import os
import re
26
import shutil
27
import stat
28
import threading
29
import time
30
31
32
import traceback

import send2trash
33
import sqlalchemy
34
from adba.aniDBAbstracter import Anime
35
from sqlalchemy import orm
echel0n's avatar
echel0n committed
36
from unidecode import unidecode
echel0n's avatar
v8.8.5    
echel0n committed
37
38

import sickrage
39
from sickrage.core.blackandwhitelist import BlackAndWhiteList
echel0n's avatar
echel0n committed
40
from sickrage.core.caches.image_cache import ImageCache
41
42
from sickrage.core.common import Quality, SKIPPED, WANTED, UNKNOWN, DOWNLOADED, IGNORED, SNATCHED, SNATCHED_PROPER, UNAIRED, ARCHIVED, statusStrings, \
    Overview, SearchFormats
43
44
from sickrage.core.databases.main import MainDB
from sickrage.core.exceptions import ShowNotFoundException, EpisodeNotFoundException, EpisodeDeletedException, MultipleEpisodesInDatabaseException
echel0n's avatar
echel0n committed
45
from sickrage.core.helpers import list_media_files, is_media_file, try_int, safe_getattr
46
from sickrage.core.nameparser import NameParser, InvalidNameException, InvalidShowException
47
from sickrage.core.tv.episode import TVEpisode
48
from sickrage.indexers import IndexerApi
49
from sickrage.indexers.exceptions import indexer_attributenotfound, indexer_exception
50
51


52
class TVShow(object):
53
    def __init__(self, indexer_id, indexer, lang='en', location=''):
54
        self.lock = threading.Lock()
55
        self._episodes = []
56

57
58
59
60
61
62
63
64
65
66
67
        with sickrage.app.main_db.session() as session:
            try:
                query = session.query(MainDB.TVShow).filter_by(indexer_id=indexer_id, indexer=indexer).one()
                self._data_local = query.as_dict()
            except orm.exc.NoResultFound:
                session.add(MainDB.TVShow(**{
                    'indexer_id': indexer_id,
                    'indexer': indexer,
                    'lang': lang,
                    'location': location
                }))
68

69
                session.commit()
70

71
72
                query = session.query(MainDB.TVShow).filter_by(indexer_id=indexer_id, indexer=indexer).one()
                self._data_local = query.as_dict()
73

74
75
                sickrage.app.shows.update({(self.indexer_id, self.indexer): self})

76
                self.load_from_indexer()
77
78
79

    @property
    def indexer_id(self):
80
        return self._data_local['indexer_id']
81
82
83

    @indexer_id.setter
    def indexer_id(self, value):
84
        self._data_local['indexer_id'] = value
85
86
87

    @property
    def indexer(self):
88
        return self._data_local['indexer']
89
90
91

    @indexer.setter
    def indexer(self, value):
92
        self._data_local['indexer'] = value
93
94
95

    @property
    def name(self):
96
        return self._data_local['name']
97
98
99

    @name.setter
    def name(self, value):
100
        self._data_local['name'] = value
101
102
103

    @property
    def location(self):
104
        return self._data_local['location']
105
106
107

    @location.setter
    def location(self, value):
108
        self._data_local['location'] = value
109
110
111

    @property
    def network(self):
112
        return self._data_local['network']
113
114
115

    @network.setter
    def network(self, value):
116
        self._data_local['network'] = value
117
118
119

    @property
    def genre(self):
120
        return self._data_local['genre']
121
122
123

    @genre.setter
    def genre(self, value):
124
        self._data_local['genre'] = value
125
126
127

    @property
    def overview(self):
128
        return self._data_local['overview']
129
130
131

    @overview.setter
    def overview(self, value):
132
        self._data_local['overview'] = value
133
134
135

    @property
    def classification(self):
136
        return self._data_local['classification']
137
138
139

    @classification.setter
    def classification(self, value):
140
        self._data_local['classification'] = value
141
142
143

    @property
    def runtime(self):
144
        return self._data_local['runtime']
145
146
147

    @runtime.setter
    def runtime(self, value):
148
        self._data_local['runtime'] = value
149
150
151

    @property
    def quality(self):
152
        return self._data_local['quality']
153
154
155

    @quality.setter
    def quality(self, value):
156
        self._data_local['quality'] = value
157
158
159

    @property
    def airs(self):
160
        return self._data_local['airs']
161
162
163

    @airs.setter
    def airs(self, value):
164
        self._data_local['airs'] = value
165
166
167

    @property
    def status(self):
168
        return self._data_local['status']
169
170
171

    @status.setter
    def status(self, value):
172
        self._data_local['status'] = value
173
174
175

    @property
    def flatten_folders(self):
176
        return self._data_local['flatten_folders']
177
178
179

    @flatten_folders.setter
    def flatten_folders(self, value):
180
        self._data_local['flatten_folders'] = value
181
182
183

    @property
    def paused(self):
184
        return self._data_local['paused']
185
186
187

    @paused.setter
    def paused(self, value):
188
        self._data_local['paused'] = value
189
190
191

    @property
    def anime(self):
192
        return self._data_local['anime']
193
194
195

    @anime.setter
    def anime(self, value):
196
        self._data_local['anime'] = value
197
198

    @property
199
200
    def search_format(self):
        return self._data_local['search_format']
201

202
203
204
    @search_format.setter
    def search_format(self, value):
        self._data_local['search_format'] = value
205
206
207

    @property
    def subtitles(self):
208
        return self._data_local['subtitles']
209
210
211

    @subtitles.setter
    def subtitles(self, value):
212
        self._data_local['subtitles'] = value
213
214
215

    @property
    def dvdorder(self):
216
        return self._data_local['dvdorder']
217
218
219

    @dvdorder.setter
    def dvdorder(self, value):
220
        self._data_local['dvdorder'] = value
221
222
223

    @property
    def skip_downloaded(self):
224
        return self._data_local['skip_downloaded']
225
226
227

    @skip_downloaded.setter
    def skip_downloaded(self, value):
228
        self._data_local['skip_downloaded'] = value
229
230
231

    @property
    def startyear(self):
232
        return self._data_local['startyear']
233
234
235

    @startyear.setter
    def startyear(self, value):
236
        self._data_local['startyear'] = value
237
238
239

    @property
    def lang(self):
240
        return self._data_local['lang']
241
242
243

    @lang.setter
    def lang(self, value):
244
        self._data_local['lang'] = value
245
246
247

    @property
    def imdb_id(self):
248
        return self._data_local['imdb_id']
249
250
251

    @imdb_id.setter
    def imdb_id(self, value):
252
        self._data_local['imdb_id'] = value
253
254
255

    @property
    def rls_ignore_words(self):
256
        return self._data_local['rls_ignore_words']
257
258
259

    @rls_ignore_words.setter
    def rls_ignore_words(self, value):
260
        self._data_local['rls_ignore_words'] = value
261
262
263

    @property
    def rls_require_words(self):
264
        return self._data_local['rls_require_words']
265
266
267

    @rls_require_words.setter
    def rls_require_words(self, value):
268
        self._data_local['rls_require_words'] = value
269
270
271

    @property
    def default_ep_status(self):
272
        return self._data_local['default_ep_status']
273
274
275

    @default_ep_status.setter
    def default_ep_status(self, value):
276
        self._data_local['default_ep_status'] = value
277
278
279

    @property
    def sub_use_sr_metadata(self):
280
        return self._data_local['sub_use_sr_metadata']
281
282
283

    @sub_use_sr_metadata.setter
    def sub_use_sr_metadata(self, value):
284
        self._data_local['sub_use_sr_metadata'] = value
285
286
287

    @property
    def notify_list(self):
288
        return self._data_local['notify_list']
289
290
291

    @notify_list.setter
    def notify_list(self, value):
292
        self._data_local['notify_list'] = value
293
294
295

    @property
    def search_delay(self):
296
        return self._data_local['search_delay']
297
298
299

    @search_delay.setter
    def search_delay(self, value):
300
        self._data_local['search_delay'] = value
301

302
303
304
305
306
307
308
309
    @property
    def scene_exceptions(self):
        return list(filter(None, self._data_local['scene_exceptions'].split(',')))

    @scene_exceptions.setter
    def scene_exceptions(self, value):
        self._data_local['scene_exceptions'] = ','.join(value)

310
311
    @property
    def last_update(self):
312
        return self._data_local['last_update']
313
314
315

    @last_update.setter
    def last_update(self, value):
316
        self._data_local['last_update'] = value
317
318
319

    @property
    def last_refresh(self):
320
        return self._data_local['last_refresh']
321
322
323

    @last_refresh.setter
    def last_refresh(self, value):
324
        self._data_local['last_refresh'] = value
325
326
327

    @property
    def last_backlog_search(self):
328
        return self._data_local['last_backlog_search']
329
330
331

    @last_backlog_search.setter
    def last_backlog_search(self, value):
332
        self._data_local['last_backlog_search'] = value
333
334
335

    @property
    def last_proper_search(self):
336
        return self._data_local['last_proper_search']
337
338
339

    @last_proper_search.setter
    def last_proper_search(self, value):
340
        self._data_local['last_proper_search'] = value
341

342
343
344
345
346
347
348
349
    @property
    def last_scene_exceptions_refresh(self):
        return self._data_local['last_scene_exceptions_refresh']

    @last_scene_exceptions_refresh.setter
    def last_scene_exceptions_refresh(self, value):
        self._data_local['last_scene_exceptions_refresh'] = value

350
    @property
351
    def episodes(self):
352
        if not self._episodes:
353
354
            with sickrage.app.main_db.session() as session:
                query = session.query(MainDB.TVShow).filter_by(indexer_id=self.indexer_id, indexer=self.indexer).one()
355
                for x in query.episodes:
356
                    self._episodes.append(TVEpisode(showid=self.indexer_id, indexer=self.indexer, season=x.season, episode=x.episode))
357
        return self._episodes
358
359
360

    @property
    def imdb_info(self):
361
362
363
        with sickrage.app.main_db.session() as session:
            query = session.query(MainDB.TVShow).filter_by(indexer_id=self.indexer_id, indexer=self.indexer).one()
            return query.imdb_info
364

365
366
    @property
    def is_anime(self):
echel0n's avatar
echel0n committed
367
        return int(self.anime) > 0
368

369
370
371
    @property
    def airs_next(self):
        _airs_next = datetime.date.min
372
373

        with sickrage.app.main_db.session() as session:
374
375
376
377
378
379
380
381
382
383
384
385
            show_query = session.query(MainDB.TVShow).filter_by(indexer_id=self.indexer_id, indexer=self.indexer).one_or_none()
            if show_query:
                query = show_query.episodes.filter(
                    MainDB.TVEpisode.season != 0,
                    MainDB.TVEpisode.airdate >= datetime.date.today(),
                    MainDB.TVEpisode.status.in_([UNAIRED, WANTED])
                ).order_by(
                    MainDB.TVEpisode.airdate
                ).first()

                if query:
                    _airs_next = query.airdate
386

387
388
389
390
391
        return _airs_next

    @property
    def airs_prev(self):
        _airs_prev = datetime.date.min
392
393

        with sickrage.app.main_db.session() as session:
394
395
396
397
398
399
400
401
402
403
404
405
            show_query = session.query(MainDB.TVShow).filter_by(indexer_id=self.indexer_id, indexer=self.indexer).one_or_none()
            if show_query:
                query = show_query.episodes.filter(
                    MainDB.TVEpisode.season != 0,
                    MainDB.TVEpisode.airdate < datetime.date.today(),
                    MainDB.TVEpisode.status != UNAIRED
                ).order_by(
                    sqlalchemy.desc(MainDB.TVEpisode.airdate)
                ).first()

                if query:
                    _airs_prev = query.airdate
406

407
408
        return _airs_prev

409
410
    @property
    def episodes_unaired(self):
411
412
        _episodes_unaired = 0

413
        with sickrage.app.main_db.session() as session:
414
415
416
417
418
419
420
421
            show_query = session.query(MainDB.TVShow).filter_by(indexer_id=self.indexer_id, indexer=self.indexer).one_or_none()
            if show_query:
                _episodes_unaired = show_query.episodes.filter(
                    MainDB.TVEpisode.season != 0,
                    MainDB.TVEpisode.status == UNAIRED
                ).count()

        return _episodes_unaired
422

423
424
    @property
    def episodes_snatched(self):
425
426
        _episodes_snatched = 0

427
        with sickrage.app.main_db.session() as session:
428
429
430
431
432
433
434
435
            show_query = session.query(MainDB.TVShow).filter_by(indexer_id=self.indexer_id, indexer=self.indexer).one_or_none()
            if show_query:
                _episodes_snatched = show_query.episodes.filter(
                    MainDB.TVEpisode.season != 0,
                    MainDB.TVEpisode.status.in_(Quality.SNATCHED + Quality.SNATCHED_BEST + Quality.SNATCHED_PROPER)
                ).count()

        return _episodes_snatched
436
437
438

    @property
    def episodes_downloaded(self):
439
440
        _episodes_downloaded = 0

441
        with sickrage.app.main_db.session() as session:
442
443
444
445
446
447
448
449
            show_query = session.query(MainDB.TVShow).filter_by(indexer_id=self.indexer_id, indexer=self.indexer).one_or_none()
            if show_query:
                _episodes_downloaded = show_query.episodes.filter(
                    MainDB.TVEpisode.season != 0,
                    MainDB.TVEpisode.status.in_(Quality.DOWNLOADED + Quality.ARCHIVED)
                ).count()

        return _episodes_downloaded
450

451
452
    @property
    def episodes_special(self):
453
454
        _episodes_special = 0

455
        with sickrage.app.main_db.session() as session:
456
457
458
459
460
461
462
            show_query = session.query(MainDB.TVShow).filter_by(indexer_id=self.indexer_id, indexer=self.indexer).one_or_none()
            if show_query:
                _episodes_special = show_query.episodes.filter(
                    MainDB.TVEpisode.season == 0
                ).count()

        return _episodes_special
463
464
465

    @property
    def episodes_special_unaired(self):
466
467
        _episodes_special_unaired = 0

468
        with sickrage.app.main_db.session() as session:
469
470
471
472
473
474
475
476
            show_query = session.query(MainDB.TVShow).filter_by(indexer_id=self.indexer_id, indexer=self.indexer).one_or_none()
            if show_query:
                _episodes_special_unaired = show_query.episodes.filter(
                    MainDB.TVEpisode.season == 0,
                    MainDB.TVEpisode.status == UNAIRED
                ).count()

        return _episodes_special_unaired
477
478
479

    @property
    def episodes_special_downloaded(self):
480
481
        _episodes_special_downloaded = 0

482
        with sickrage.app.main_db.session() as session:
483
484
485
486
487
488
489
490
            show_query = session.query(MainDB.TVShow).filter_by(indexer_id=self.indexer_id, indexer=self.indexer).one_or_none()
            if show_query:
                _episodes_special_downloaded = show_query.episodes.filter(
                    MainDB.TVEpisode.season == 0,
                    MainDB.TVEpisode.status.in_(Quality.DOWNLOADED + Quality.ARCHIVED)
                ).count()

        return _episodes_special_downloaded
491
492
493

    @property
    def episodes_special_snatched(self):
494
495
        _episodes_special_snatched = 0

496
        with sickrage.app.main_db.session() as session:
497
498
499
500
501
502
503
504
            show_query = session.query(MainDB.TVShow).filter_by(indexer_id=self.indexer_id, indexer=self.indexer).one_or_none()
            if show_query:
                _episodes_special_snatched = show_query.episodes.filter(
                    MainDB.TVEpisode.season == 0,
                    MainDB.TVEpisode.status.in_(Quality.SNATCHED + Quality.SNATCHED_BEST + Quality.SNATCHED_PROPER)
                ).count()

        return _episodes_special_snatched
505

506
507
508
509
510
511
512
    @property
    def new_episodes(self):
        cur_date = datetime.date.today()
        cur_date += datetime.timedelta(days=1)
        cur_time = datetime.datetime.now(sickrage.app.tz)

        new_episodes = []
513
        for episode_object in self.episodes:
514
            if episode_object.status != UNAIRED or episode_object.season == 0 or not episode_object.airdate > datetime.date.min:
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
                continue

            air_date = episode_object.airdate
            air_date += datetime.timedelta(days=episode_object.show.search_delay)
            if not cur_date >= air_date:
                continue

            if episode_object.show.airs and episode_object.show.network:
                # This is how you assure it is always converted to local time
                air_time = sickrage.app.tz_updater.parse_date_time(episode_object.airdate,
                                                                   episode_object.show.airs,
                                                                   episode_object.show.network).astimezone(sickrage.app.tz)

                # filter out any episodes that haven't started airing yet,
                # but set them to the default status while they are airing
                # so they are snatched faster
                if air_time > cur_time:
                    continue

            new_episodes += [episode_object]

        return new_episodes

538
539
540
    @property
    def total_size(self):
        _total_size = 0
541
        _related_episodes = set()
542
        for episode_object in self.episodes:
543
544
545
            [_related_episodes.add(related_episode.indexer_id) for related_episode in episode_object.related_episodes]
            if episode_object.indexer_id not in _related_episodes:
                _total_size += episode_object.file_size
546
547
        return _total_size

548
549
    @property
    def network_logo_name(self):
echel0n's avatar
echel0n committed
550
        return unidecode(self.network).lower()
551

552
    @property
553
554
    def release_groups(self):
        if self.is_anime:
555
            return BlackAndWhiteList(self.indexer_id)
556

557
    def save(self):
558
559
560
561
562
563
        with sickrage.app.main_db.session() as session:
            sickrage.app.log.debug("{0:d}: Saving to database: {1}".format(self.indexer_id, self.name))
            query = session.query(MainDB.TVShow).filter_by(indexer_id=self.indexer_id, indexer=self.indexer).one_or_none()
            if query:
                query.update(**self._data_local)
            session.commit()
564

565
    def delete(self):
566
567
568
        with sickrage.app.main_db.session() as session:
            session.query(MainDB.TVShow).filter_by(indexer_id=self.indexer_id, indexer=self.indexer).delete()
            session.commit()
569

570
    def flush_episodes(self):
571
        self._episodes.clear()
572

573
    def load_from_indexer(self, cache=True, tvapi=None):
574
        sickrage.app.log.debug(str(self.indexer_id) + ": Loading show info from " + IndexerApi(self.indexer).name)
echel0n's avatar
echel0n committed
575

576
577
578
579
        t = tvapi
        if not t:
            lINDEXER_API_PARMS = IndexerApi(self.indexer).api_params.copy()
            lINDEXER_API_PARMS['cache'] = cache
echel0n's avatar
echel0n committed
580

581
            lINDEXER_API_PARMS['language'] = self.lang or sickrage.app.config.indexer_default_language
echel0n's avatar
echel0n committed
582

583
584
            if self.dvdorder != 0:
                lINDEXER_API_PARMS['dvdorder'] = True
echel0n's avatar
echel0n committed
585

586
            t = IndexerApi(self.indexer).indexer(**lINDEXER_API_PARMS)
echel0n's avatar
echel0n committed
587

588
589
590
        myEp = t[self.indexer_id]
        if not myEp:
            raise indexer_exception
echel0n's avatar
echel0n committed
591

592
593
594
595
        try:
            self.name = myEp['seriesname'].strip()
        except AttributeError:
            raise indexer_attributenotfound("Found %s, but attribute 'seriesname' was empty." % self.indexer_id)
echel0n's avatar
echel0n committed
596

597
598
599
600
601
602
        self.overview = safe_getattr(myEp, 'overview', self.overview)
        self.classification = safe_getattr(myEp, 'classification', self.classification)
        self.genre = safe_getattr(myEp, 'genre', self.genre)
        self.network = safe_getattr(myEp, 'network', self.network)
        self.runtime = try_int(safe_getattr(myEp, 'runtime', self.runtime))
        self.imdb_id = safe_getattr(myEp, 'imdbid', self.imdb_id)
echel0n's avatar
echel0n committed
603

604
605
606
607
        try:
            self.airs = (safe_getattr(myEp, 'airsdayofweek') + " " + safe_getattr(myEp, 'airstime')).strip()
        except:
            self.airs = ''
echel0n's avatar
echel0n committed
608

609
610
611
612
        try:
            self.startyear = try_int(str(safe_getattr(myEp, 'firstaired') or datetime.date.min).split('-')[0])
        except:
            self.startyear = 0
echel0n's avatar
echel0n committed
613

614
        self.status = safe_getattr(myEp, 'status', self.status)
615

616
        self.save()
echel0n's avatar
echel0n committed
617

618
    def load_episodes_from_indexer(self, cache=True):
619
620
621
622
623
624
625
626
627
628
629
630
        scanned_eps = {}

        l_indexer_api_parms = IndexerApi(self.indexer).api_params.copy()
        l_indexer_api_parms['cache'] = cache

        l_indexer_api_parms['language'] = self.lang or sickrage.app.config.indexer_default_language

        if self.dvdorder != 0:
            l_indexer_api_parms['dvdorder'] = True

        t = IndexerApi(self.indexer).indexer(**l_indexer_api_parms)

631
632
633
634
        sickrage.app.log.debug(str(self.indexer_id) + ": Loading all episodes from " + IndexerApi(self.indexer).name + "..")

        # flush episodes from cache so we can reload from database
        self.flush_episodes()
635

636
637
638
639
640
        indexer_data = t[self.indexer_id]
        if not indexer_data:
            raise indexer_exception

        for season in indexer_data:
641
            scanned_eps[season] = {}
642
            for episode in indexer_data[season]:
643
644
645
646
647
                # need some examples of wtf episode 0 means to decide if we want it or not
                if episode == 0:
                    continue

                try:
648
                    episode_obj = self.get_episode(season, episode)
649
                except EpisodeNotFoundException:
650
                    continue
651
652
653
654
655
656
                else:
                    try:
                        episode_obj.load_from_indexer(season, episode)
                    except EpisodeDeletedException:
                        sickrage.app.log.info("The episode was deleted, skipping the rest of the load")
                        continue
657
658
659
660
661
662

                scanned_eps[season][episode] = True

        # Done updating save last update date
        self.last_update = datetime.date.today().toordinal()

663
        self.save()
664

665
        return scanned_eps
echel0n's avatar
echel0n committed
666

667
    def get_episode(self, season=None, episode=None, absolute_number=None, no_create=False):
668
        try:
669
            if season is None and episode is None and absolute_number is not None:
670
671
672
673
674
                with sickrage.app.main_db.session() as session:
                    query = session.query(MainDB.TVEpisode).filter_by(showid=self.indexer_id, indexer=self.indexer, absolute_number=absolute_number).one()
                    sickrage.app.log.debug("Found episode by absolute_number %s which is S%02dE%02d" % (absolute_number, query.season, query.episode))
                    season = query.season
                    episode = query.episode
675

676
677
678
679
            for tv_episode in self._episodes:
                if tv_episode.season == season and tv_episode.episode == episode:
                    return tv_episode
            else:
680
681
                if no_create:
                    raise EpisodeNotFoundException
682
                tv_episode = TVEpisode(showid=self.indexer_id, indexer=self.indexer, season=season, episode=episode)
683
684
                self._episodes.append(tv_episode)
                self._episodes = list(set(self._episodes))
685
                return tv_episode
686
687
        except orm.exc.MultipleResultsFound:
            if absolute_number is not None:
688
                sickrage.app.log.debug("Multiple entries for absolute number: " + str(absolute_number) + " in show: " + self.name + " found ")
689
690
691
692
693
            raise MultipleEpisodesInDatabaseException
        except orm.exc.NoResultFound:
            if absolute_number is not None:
                sickrage.app.log.debug("No entries for absolute number: " + str(absolute_number) + " in show: " + self.name + " found.")
            raise EpisodeNotFoundException
694

695
    def write_show_nfo(self, force=False):
696
697
        result = False

698
        if not os.path.isdir(self.location):
699
            sickrage.app.log.info(str(self.indexer_id) + ": Show dir doesn't exist, skipping NFO generation")
700
701
            return False

702
        sickrage.app.log.debug(str(self.indexer_id) + ": Writing NFOs for show")
703
        for cur_provider in sickrage.app.metadata_providers.values():
704
            result = cur_provider.create_show_metadata(self, force) or result
705
706
707

        return result

708
    def write_metadata(self, show_only=False, force=False):
709
        if not os.path.isdir(self.location):
710
            sickrage.app.log.info(str(self.indexer_id) + ": Show dir doesn't exist, skipping NFO generation")
711
712
            return

713
        self.get_images()
714

715
        self.write_show_nfo(force)
716
717

        if not show_only:
718
            self.write_episode_nfos(force)
719
            self.update_episode_video_metadata()
720

721
    def write_episode_nfos(self, force=False):
722
        if not os.path.isdir(self.location):
723
            sickrage.app.log.info(str(self.indexer_id) + ": Show dir doesn't exist, skipping NFO generation")
724
725
            return

726
        sickrage.app.log.debug(str(self.indexer_id) + ": Writing NFOs for all episodes")
727

728
        for episode_obj in self.episodes:
729
            if episode_obj.location == '':
730
731
                continue

732
            sickrage.app.log.debug(str(self.indexer_id) + ": Retrieving/creating episode S%02dE%02d" % (episode_obj.season or 0, episode_obj.episode or 0))
echel0n's avatar
echel0n committed
733

734
            episode_obj.create_meta_files(force)
735

736
737
738
739
740
741
742
    def update_episode_video_metadata(self):
        if not os.path.isdir(self.location):
            sickrage.app.log.info(str(self.indexer_id) + ": Show dir doesn't exist, skipping video metadata updating")
            return

        sickrage.app.log.debug(str(self.indexer_id) + ": Updating video metadata for all episodes")

743
        for episode_obj in self.episodes:
744
745
746
            if episode_obj.location == '':
                continue

747
            sickrage.app.log.debug(str(self.indexer_id) + ": Updating video metadata for episode S%02dE%02d" % (episode_obj.season, episode_obj.episode))
748
749
750

            episode_obj.update_video_metadata()

751
    # find all media files in the show folder and create episodes for as many as possible
752
    def load_episodes_from_dir(self):
753
        if not os.path.isdir(self.location):
754
            sickrage.app.log.debug(str(self.indexer_id) + ": Show dir doesn't exist, not loading episodes from disk")
755
756
            return

757
        sickrage.app.log.debug(str(self.indexer_id) + ": Loading all episodes from the show directory " + self.location)
758
759

        # get file list
760
        media_files = list_media_files(self.location)
761
762

        # create TVEpisodes from each media file (if possible)
763
        for mediaFile in media_files:
764
765
            curEpisode = None

766
            sickrage.app.log.debug(str(self.indexer_id) + ": Creating episode from " + mediaFile)
767
            try:
768
                curEpisode = self.make_ep_from_file(os.path.join(self.location, mediaFile))
769
            except (ShowNotFoundException, EpisodeNotFoundException) as e:
echel0n's avatar
echel0n committed
770
                sickrage.app.log.warning("Episode " + mediaFile + " returned an exception: {}".format(e))
771
            except EpisodeDeletedException:
772
                sickrage.app.log.debug("The episode deleted itself when I tried making an object for it")
773

774
775
            # skip to next episode?
            if not curEpisode:
776
777
778
779
780
781
782
                continue

            # see if we should save the release name in the db
            ep_file_name = os.path.basename(curEpisode.location)
            ep_file_name = os.path.splitext(ep_file_name)[0]

            try:
783
                parse_result = NameParser(False, show_id=self.indexer_id).parse(ep_file_name)
784
            except (InvalidNameException, InvalidShowException):
echel0n's avatar
echel0n committed
785
                parse_result = None
786

787
            if ' ' not in ep_file_name and parse_result and parse_result.release_group:
788
                sickrage.app.log.debug("Name " + ep_file_name + " gave release group of " + parse_result.release_group + ", seems valid")
789
                curEpisode.release_name = ep_file_name
790
                self.save()
791
792

            # store the reference in the show
793
            if self.subtitles and sickrage.app.config.use_subtitles:
794
                try:
795
                    curEpisode.refresh_subtitles()
796
                except Exception:
797
                    sickrage.app.log.error("%s: Could not refresh subtitles" % self.indexer_id)
798
                    sickrage.app.log.debug(traceback.format_exc())
799

800
801
802
803
804
805
806
807
    def load_imdb_info(self):
        imdb_info_mapper = {
            'imdbvotes': 'votes',
            'imdbrating': 'rating',
            'totalseasons': 'seasons',
            'imdbid': 'imdb_id'
        }

808
        if not re.search(r'^tt\d+$', self.imdb_id) and self.name:
809
810
            resp = sickrage.app.api.imdb.search_by_imdb_title(self.name)
            if not resp:
echel0n's avatar
echel0n committed
811
812
813
                resp = {}

            for x in resp.get('Search', []):
814
815
                try:
                    if int(x.get('Year'), 0) == self.startyear and x.get('Title') in self.name:
816
                        if re.search(r'^tt\d+$', x.get('imdbID', '')):
817
818
819
                            self.imdb_id = x.get('imdbID')
                            self.save()
                            break
820
                except Exception:
821
822
                    continue

823
        if re.search(r'^tt\d+$', self.imdb_id):
824
825
            sickrage.app.log.debug(str(self.indexer_id) + ": Obtaining IMDb info")

826
            imdb_info = sickrage.app.api.imdb.search_by_imdb_id(self.imdb_id)
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
            if not imdb_info:
                sickrage.app.log.debug(str(self.indexer_id) + ': Unable to obtain IMDb info')
                return

            imdb_info = dict((k.lower(), v) for k, v in imdb_info.items())
            for column in imdb_info.copy():
                if column in imdb_info_mapper:
                    imdb_info[imdb_info_mapper[column]] = imdb_info[column]

                if column not in MainDB.IMDbInfo.__table__.columns.keys():
                    del imdb_info[column]

            if not all([imdb_info.get('imdb_id'), imdb_info.get('votes'), imdb_info.get('rating'), imdb_info.get('genre')]):
                sickrage.app.log.debug(str(self.indexer_id) + ': IMDb info obtained does not meet our requirements')
                return

            sickrage.app.log.debug(str(self.indexer_id) + ": Obtained IMDb info ->" + str(imdb_info))

            # save imdb info to database
            imdb_info.update({
                'indexer_id': self.indexer_id,
                'last_update': datetime.date.today().toordinal()
            })

851
852
853
854
855
856
857
858
            with sickrage.app.main_db.session() as session:
                try:
                    dbData = session.query(MainDB.IMDbInfo).filter_by(indexer_id=self.indexer_id).one()
                    dbData.update(**imdb_info)
                except orm.exc.NoResultFound:
                    session.add(MainDB.IMDbInfo(**imdb_info))
                finally:
                    self.save()
859

860
    def get_images(self, fanart=None, poster=None):
861
862
863
        fanart_result = poster_result = banner_result = False
        season_posters_result = season_banners_result = season_all_poster_result = season_all_banner_result = False

864
        for cur_provider in sickrage.app.metadata_providers.values():
865
866
            fanart_result = cur_provider.create_fanart(self) or fanart_result
            poster_result = cur_provider.create_poster(self) or poster_result
867
            banner_result = cur_provider.create_banner(self) or banner_result
868
869
870
871
872
873
874
875

            season_posters_result = cur_provider.create_season_posters(self) or season_posters_result
            season_banners_result = cur_provider.create_season_banners(self) or season_banners_result
            season_all_poster_result = cur_provider.create_season_all_poster(self) or season_all_poster_result
            season_all_banner_result = cur_provider.create_season_all_banner(self) or season_all_banner_result

        return fanart_result or poster_result or banner_result or season_posters_result or season_banners_result or season_all_poster_result or season_all_banner_result

876
877
    def make_ep_from_file(self, filename):
        if not os.path.isfile(filename):
878
            sickrage.app.log.info(str(self.indexer_id) + ": That isn't even a real file dude... " + filename)
879
880
            return None

881
        sickrage.app.log.debug(str(self.indexer_id) + ": Creating episode object from " + filename)
882
883

        try:
884
            parse_result = NameParser(validate_show=False).parse(filename, skip_scene_detection=True)
885
        except InvalidNameException:
886
            sickrage.app.log.debug("Unable to parse the filename " + filename + " into a valid episode")
887
888
            return None
        except InvalidShowException:
889
            sickrage.app.log.debug("Unable to parse the filename " + filename + " into a valid show")
890
891
892
            return None

        if not len(parse_result.episode_numbers):
893
            sickrage.app.log.info("parse_result: " + str(parse_result))
894
            sickrage.app.log.warning("No episode number found in " + filename + ", ignoring it")
895
896
897
898
            return None

        # for now lets assume that any episode in the show dir belongs to that show
        season = parse_result.season_number if parse_result.season_number is not None else 1
899
        root_ep = None
900

901
        for curEpNum in parse_result.episode_numbers:
902
903
            episode = int(curEpNum)

904
            sickrage.app.log.debug("%s: %s parsed to %s S%02dE%02d" % (self.indexer_id, filename, self.name, int(season or 0), int(episode or 0)))
905

906
            check_quality_again = False
907

908
909
910
            try:
                episode_obj = self.get_episode(season, episode)
            except EpisodeNotFoundException:
911
912
                sickrage.app.log.warning("{}: Unable to figure out what this file is, skipping {}".format(self.indexer_id, filename))
                continue
913
914
915
916
917
918
919
920
921
922
923
924
925
926