Completed
Push — dev ( 7b477b...154fc3 )
by Darko
07:52 queued 01:35
created

TMDB   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 395
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 5
Bugs 2 Features 0
Metric Value
wmc 61
eloc 168
c 5
b 2
f 0
dl 0
loc 395
ccs 0
cts 155
cp 0
rs 3.52

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getBanner() 0 3 1
A getPoster() 0 17 3
A getShowInfo() 0 17 4
F processSite() 0 126 28
B matchShowInfo() 0 46 11
A formatEpisodeInfo() 0 9 1
B formatShowInfo() 0 24 8
A getEpisodeInfo() 0 18 4
A __construct() 0 13 1

How to fix   Complexity   

Complex Class

Complex classes like TMDB often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TMDB, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Blacklight\processing\tv;
4
5
use Tmdb\Client;
6
use Tmdb\ApiToken;
7
use Blacklight\ReleaseImage;
8
use Tmdb\Exception\TmdbApiException;
9
use Tmdb\Helper\ImageHelper;
10
use Tmdb\Laravel\Facades\Tmdb as TmdbClient;
11
use Tmdb\Repository\ConfigurationRepository;
12
13
class TMDB extends TV
14
{
15
    protected const MATCH_PROBABILITY = 75;
16
17
    /**
18
     * @var string The URL for the image for poster
19
     */
20
    public $posterUrl;
21
    /**
22
     * @var ApiToken
23
     */
24
    public $token;
25
    /**
26
     * @var Client
27
     */
28
    public $client;
29
    /**
30
     * @var ConfigurationRepository
31
     */
32
    public $configRepository;
33
    /**
34
     * @var \Tmdb\Model\Configuration
35
     */
36
    public $config;
37
    /**
38
     * @var ImageHelper
39
     */
40
    public $helper;
41
42
    /**
43
     * Construct. Instantiate TMDB Class.
44
     *
45
     * @param array $options Class instances.
46
     *
47
     * @throws \Exception
48
     */
49
    public function __construct(array $options = [])
50
    {
51
        parent::__construct($options);
52
        $this->token = new ApiToken(config('tmdb.api_key'));
53
        $this->client = new Client($this->token, [
54
                'cache' => [
55
                    'enabled' => false,
56
                ],
57
            ]
58
        );
59
        $this->configRepository = new ConfigurationRepository($this->client);
60
        $this->config = $this->configRepository->load();
61
        $this->helper = new ImageHelper($this->config);
62
    }
63
64
    /**
65
     * Fetch banner from site.
66
     *
67
     * @param $videoId
68
     * @param $siteID
69
     *
70
     * @return bool
71
     */
72
    public function getBanner($videoId, $siteID): bool
73
    {
74
        return false;
75
    }
76
77
    /**
78
     * Main processing director function for TMDB
79
     * Calls work query function and initiates processing.
80
     *
81
     * @param      $groupID
82
     * @param      $guidChar
83
     * @param      $process
84
     * @param bool $local
85
     */
86
    public function processSite($groupID, $guidChar, $process, $local = false): void
87
    {
88
        $res = $this->getTvReleases($groupID, $guidChar, $process, parent::PROCESS_TMDB);
89
90
        $tvcount = \count($res);
0 ignored issues
show
Bug introduced by
It seems like $res can also be of type integer; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

90
        $tvcount = \count(/** @scrutinizer ignore-type */ $res);
Loading history...
91
        $lookupSetting = true;
92
93
        if ($this->echooutput && $tvcount > 0) {
94
            $this->colorCli->header('Processing TMDB lookup for '.number_format($tvcount).' release(s).', true);
95
        }
96
97
        if ($res instanceof \Traversable) {
98
            $this->titleCache = [];
99
100
            foreach ($res as $row) {
101
                $tmdbid = false;
102
103
                // Clean the show name for better match probability
104
                $release = $this->parseInfo($row['searchname']);
105
106
                if (\is_array($release) && $release['name'] !== '') {
107
                    if (\in_array($release['cleanname'], $this->titleCache, false)) {
108
                        if ($this->echooutput) {
109
                            $this->colorCli->headerOver('Title: ').
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->colorCli->headerOver('Title: ') targeting Blacklight\ColorCLI::headerOver() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure $this->colorCli->headerOver('Title: ') of type void can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

109
                            /** @scrutinizer ignore-type */ $this->colorCli->headerOver('Title: ').
Loading history...
110
                                    $this->colorCli->warningOver($release['cleanname']).
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->colorCli->warning...($release['cleanname']) targeting Blacklight\ColorCLI::warningOver() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
111
                                    $this->colorCli->header(' already failed lookup for this site.  Skipping.', true);
0 ignored issues
show
Bug introduced by
Are you sure $this->colorCli->header(...ite. Skipping.', true) of type void can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

111
                                    /** @scrutinizer ignore-type */ $this->colorCli->header(' already failed lookup for this site.  Skipping.', true);
Loading history...
Bug introduced by
Are you sure the usage of $this->colorCli->header(...ite. Skipping.', true) targeting Blacklight\ColorCLI::header() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
112
                        }
113
                        $this->setVideoNotFound(parent::PROCESS_TRAKT, $row['id']);
114
                        continue;
115
                    }
116
117
                    // Find the Video ID if it already exists by checking the title against stored TMDB titles
118
                    $videoId = $this->getByTitle($release['cleanname'], parent::TYPE_TV, parent::SOURCE_TMDB);
119
120
                    // Force local lookup only
121
                    if ($local === true) {
122
                        $lookupSetting = false;
123
                    }
124
125
                    // If lookups are allowed lets try to get it.
126
                    if ($videoId === false && $lookupSetting) {
127
                        if ($this->echooutput) {
128
                            $this->colorCli->primaryOver('Checking TMDB for previously failed title: ').
0 ignored issues
show
Bug introduced by
Are you sure $this->colorCli->primary...iously failed title: ') of type void can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

128
                            /** @scrutinizer ignore-type */ $this->colorCli->primaryOver('Checking TMDB for previously failed title: ').
Loading history...
Bug introduced by
Are you sure the usage of $this->colorCli->primary...iously failed title: ') targeting Blacklight\ColorCLI::primaryOver() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
129
                                    $this->colorCli->headerOver($release['cleanname']).
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->colorCli->headerO...($release['cleanname']) targeting Blacklight\ColorCLI::headerOver() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
130
                                    $this->colorCli->primary('.', true);
0 ignored issues
show
Bug introduced by
Are you sure $this->colorCli->primary('.', true) of type void can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

130
                                    /** @scrutinizer ignore-type */ $this->colorCli->primary('.', true);
Loading history...
Bug introduced by
Are you sure the usage of $this->colorCli->primary('.', true) targeting Blacklight\ColorCLI::primary() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
131
                        }
132
133
                        // Get the show from TMDB
134
                        $tmdbShow = $this->getShowInfo((string) $release['cleanname']);
135
136
                        if (\is_array($tmdbShow)) {
137
                            // Check if we have the TMDB ID already, if we do use that Video ID
138
                            $dupeCheck = $this->getVideoIDFromSiteID('tvdb', $tmdbShow['tvdb']);
139
                            if ($dupeCheck === false) {
140
                                $videoId = $this->add($tmdbShow);
141
                                $tmdbid = $tmdbShow['tmdb'];
142
                            } else {
143
                                $videoId = $dupeCheck;
144
                                // Update any missing fields and add site IDs
145
                                $this->update($videoId, $tmdbShow);
146
                                $tmdbid = $this->getSiteIDFromVideoID('tmdb', $videoId);
147
                            }
148
                        }
149
                    } else {
150
                        if ($this->echooutput) {
151
                            $this->colorCli->primaryOver('Found local TMDB match for: '.$release['cleanname']);
152
                            $this->colorCli->primary('.  Attempting episode lookup!', true);
153
                        }
154
                        $tmdbid = $this->getSiteIDFromVideoID('tmdb', $videoId);
0 ignored issues
show
Bug introduced by
It seems like $videoId can also be of type false; however, parameter $videoID of Blacklight\processing\Vi...:getSiteIDFromVideoID() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

154
                        $tmdbid = $this->getSiteIDFromVideoID('tmdb', /** @scrutinizer ignore-type */ $videoId);
Loading history...
155
                    }
156
157
                    if (is_numeric($videoId) && $videoId > 0 && is_numeric($tmdbid) && $tmdbid > 0) {
158
                        // Now that we have valid video and tmdb ids, try to get the poster
159
                        $this->getPoster($videoId);
160
161
                        $seasonNo = preg_replace('/^S0*/i', '', $release['season']);
162
                        $episodeNo = preg_replace('/^E0*/i', '', $release['episode']);
163
164
                        if ($episodeNo === 'all') {
165
                            // Set the video ID and leave episode 0
166
                            $this->setVideoIdFound($videoId, $row['id'], 0);
167
                            $this->colorCli->primary('Found TMDB Match for Full Season!', true);
168
                            continue;
169
                        }
170
171
                        // Download all episodes if new show to reduce API usage
172
                        if ($this->countEpsByVideoID($videoId) === false) {
173
                            $this->getEpisodeInfo($tmdbid, -1, -1, '', $videoId);
174
                        }
175
176
                        // Check if we have the episode for this video ID
177
                        $episode = $this->getBySeasonEp($videoId, $seasonNo, $episodeNo, $release['airdate']);
178
179
                        if ($episode === false) {
180
                            // Send the request for the episode to TMDB
181
                            $tmdbEpisode = $this->getEpisodeInfo(
182
                                $tmdbid,
183
                                $seasonNo,
184
                                $episodeNo,
185
                                $release['airdate']
186
                            );
187
188
                            if ($tmdbEpisode) {
189
                                $episode = $this->addEpisode($videoId, $tmdbEpisode);
190
                            }
191
                        }
192
193
                        if ($episode !== false && is_numeric($episode) && $episode > 0) {
194
                            // Mark the releases video and episode IDs
195
                            $this->setVideoIdFound($videoId, $row['id'], $episode);
196
                            if ($this->echooutput) {
197
                                $this->colorCli->primary('Found TMDB Match!', true);
198
                            }
199
                            continue;
200
                        }
201
                        //Processing failed, set the episode ID to the next processing group
202
                        $this->setVideoNotFound(parent::PROCESS_TRAKT, $row['id']);
203
                    } else {
204
                        //Processing failed, set the episode ID to the next processing group
205
                        $this->setVideoNotFound(parent::PROCESS_TRAKT, $row['id']);
206
                        $this->titleCache[] = $release['cleanname'];
207
                    }
208
                } else {
209
                    //Processing failed, set the episode ID to the next processing group
210
                    $this->setVideoNotFound(parent::PROCESS_TRAKT, $row['id']);
211
                    $this->titleCache[] = $release['cleanname'];
212
                }
213
            }
214
        }
215
    }
216
217
    /**
218
     * Calls the API to perform initial show name match to TMDB title
219
     * Returns a formatted array of show data or false if no match.
220
     *
221
     * @param $cleanName
222
     *
223
     * @return array|false
224
     */
225
    protected function getShowInfo($cleanName)
226
    {
227
        $return = $response = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
228
229
        try {
230
            $response = TmdbClient::getSearchApi()->searchTv($cleanName);
231
        } catch (TmdbApiException $e) {
232
            return false;
233
        }
234
235
        sleep(1);
236
237
        if (\is_array($response) && ! empty($response['results'])) {
238
            $return = $this->matchShowInfo($response['results'], $cleanName);
239
        }
240
241
        return $return;
242
    }
243
244
    /**
245
     * @param array $shows
246
     * @param string $cleanName
247
     *
248
     * @return array|false
249
     */
250
    private function matchShowInfo($shows, $cleanName)
251
    {
252
        $return = false;
253
        $highestMatch = 0;
254
255
        $show = [];
256
        foreach ($shows as $show) {
257
            if ($this->checkRequiredAttr($show, 'tmdbS')) {
0 ignored issues
show
Bug introduced by
'tmdbS' of type string is incompatible with the type integer expected by parameter $type of Blacklight\processing\tv\TV::checkRequiredAttr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

257
            if ($this->checkRequiredAttr($show, /** @scrutinizer ignore-type */ 'tmdbS')) {
Loading history...
258
                // Check for exact title match first and then terminate if found
259
                if (strtolower($show['name']) === strtolower($cleanName)) {
260
                    $highest = $show;
261
                    break;
262
                }
263
                // Check each show title for similarity and then find the highest similar value
264
                $matchPercent = $this->checkMatch(strtolower($show['name']), strtolower($cleanName), self::MATCH_PROBABILITY);
265
266
                // If new match has a higher percentage, set as new matched title
267
                if ($matchPercent > $highestMatch) {
268
                    $highestMatch = $matchPercent;
269
                    $highest = $show;
270
                }
271
            }
272
        }
273
        if (! empty($highest)) {
274
            try {
275
                $showAlternativeTitles = TmdbClient::getTvApi()->getAlternativeTitles($highest['id']);
276
            } catch (TmdbApiException $e) {
277
                return false;
278
            }
279
            try {
280
                $showExternalIds = TmdbClient::getTvApi()->getExternalIds($highest['id']);
281
            } catch (TmdbApiException $e) {
282
                return false;
283
            }
284
285
            if ($showAlternativeTitles !== null && \is_array($showAlternativeTitles)) {
286
                foreach ($showAlternativeTitles['results'] as $aka) {
287
                    $highest['alternative_titles'][] = $aka['title'];
288
                }
289
                $highest['network'] = $show['networks'][0]['name'] ?? '';
290
                $highest['external_ids'] = $showExternalIds;
291
            }
292
            $return = $this->formatShowInfo($highest);
293
        }
294
295
        return $return;
296
    }
297
298
    /**
299
     * Retrieves the poster art for the processed show.
300
     *
301
     * @param int $videoId -- the local Video ID
302
     *
303
     * @return int
304
     */
305
    public function getPoster($videoId): int
306
    {
307
        $ri = new ReleaseImage();
308
309
        $hascover = 0;
310
311
        // Try to get the Poster
312
        if (! empty($this->posterUrl)) {
313
            $hascover = $ri->saveImage($videoId, $this->posterUrl, $this->imgSavePath);
314
315
            // Mark it retrieved if we saved an image
316
            if ($hascover === 1) {
317
                $this->setCoverFound($videoId);
318
            }
319
        }
320
321
        return $hascover;
322
    }
323
324
    /**
325
     * Gets the specific episode info for the parsed release after match
326
     * Returns a formatted array of episode data or false if no match.
327
     *
328
     * @param int $tmdbid
329
     * @param int $season
330
     * @param int $episode
331
     * @param string  $airdate
332
     * @param int $videoId
333
     *
334
     * @return array|false
335
     */
336
    protected function getEpisodeInfo($tmdbid, $season, $episode, $airdate = '', $videoId = 0)
337
    {
338
        $return = false;
339
340
        try {
341
            $response = TmdbClient::getTvEpisodeApi()->getEpisode($tmdbid, $season, $episode);
342
        } catch (TmdbApiException $e) {
343
            return false;
344
        }
345
346
        sleep(1);
347
348
        //Handle Single Episode Lookups
349
        if (\is_array($response) && $this->checkRequiredAttr($response, 'tmdbE')) {
0 ignored issues
show
Bug introduced by
'tmdbE' of type string is incompatible with the type integer expected by parameter $type of Blacklight\processing\tv\TV::checkRequiredAttr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

349
        if (\is_array($response) && $this->checkRequiredAttr($response, /** @scrutinizer ignore-type */ 'tmdbE')) {
Loading history...
350
            $return = $this->formatEpisodeInfo($response);
351
        }
352
353
        return $return;
354
    }
355
356
    /**
357
     * Assigns API show response values to a formatted array for insertion
358
     * Returns the formatted array.
359
     *
360
     * @param $show
361
     *
362
     * @return array
363
     */
364
    protected function formatShowInfo($show): array
365
    {
366
        $this->posterUrl = isset($show['poster_path']) ? 'https:'.$this->helper->getUrl($show['poster_path']) : '';
367
368
        if (isset($show['external_ids']['imdb_id'])) {
369
            preg_match('/tt(?P<imdbid>\d{6,7})$/i', $show['external_ids']['imdb_id'], $imdb);
370
        }
371
372
        return [
373
                'type'      => parent::TYPE_TV,
374
                'title'     => (string) $show['name'],
375
                'summary'   => (string) $show['overview'],
376
                'started'   => (string) $show['first_air_date'],
377
                'publisher' => isset($show['network']) ? (string) $show['network'] : '',
378
                'country'   => $show['origin_country'][0] ?? '',
379
                'source'    => parent::SOURCE_TMDB,
380
                'imdb'      => isset($imdb['imdbid']) ? (int) $imdb['imdbid'] : 0,
381
                'tvdb'      => isset($show['external_ids']['tvdb_id']) ? (int) $show['external_ids']['tvdb_id'] : 0,
382
                'trakt'     => 0,
383
                'tvrage'    => isset($show['external_ids']['tvrage_id']) ? (int) $show['external_ids']['tvrage_id'] : 0,
384
                'tvmaze'    => 0,
385
                'tmdb'      => (int) $show['id'],
386
                'aliases'   => ! empty($show['alternative_titles']) ? (array) $show['alternative_titles'] : '',
387
                'localzone' => "''",
388
        ];
389
    }
390
391
    /**
392
     * Assigns API episode response values to a formatted array for insertion
393
     * Returns the formatted array.
394
     *
395
     * @param $episode
396
     *
397
     * @return array
398
     */
399
    protected function formatEpisodeInfo($episode): array
400
    {
401
        return [
402
                'title'       => (string) $episode['name'],
403
                'series'      => (int) $episode['season_number'],
404
                'episode'     => (int) $episode['episode_number'],
405
                'se_complete' => 'S'.sprintf('%02d', $episode['season_number']).'E'.sprintf('%02d', $episode['episode_number']),
406
                'firstaired'  => (string) $episode['air_date'],
407
                'summary'     => (string) $episode['overview'],
408
        ];
409
    }
410
}
411