Passed
Push — master ( 5bacce...8b334f )
by Darko
10:34
created

TvdbPipe::getName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace App\Services\TvProcessing\Pipes;
4
5
use App\Services\FanartTvService;
6
use App\Services\TvProcessing\TvProcessingPassable;
7
use App\Services\TvProcessing\TvProcessingResult;
8
use App\Services\TvProcessing\Providers\TvdbProvider;
9
10
/**
11
 * Pipe for TVDB API lookups.
12
 */
13
class TvdbPipe extends AbstractTvProviderPipe
14
{
15
    // Video type constants (matching Videos class protected constants)
16
    private const TYPE_TV = 0;
17
18
    protected int $priority = 20;
19
    private ?TvdbProvider $tvdb = null;
20
    private ?FanartTvService $fanart = null;
21
22
    public function getName(): string
23
    {
24
        return 'TVDB';
25
    }
26
27
    public function getStatusCode(): int
28
    {
29
        return 0; // PROCESS_TVDB
30
    }
31
32
    /**
33
     * Get or create the TVDB instance.
34
     */
35
    private function getTvdb(): TvdbProvider
36
    {
37
        if ($this->tvdb === null) {
38
            $this->tvdb = new TvdbProvider();
39
        }
40
        return $this->tvdb;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->tvdb could return the type null which is incompatible with the type-hinted return App\Services\TvProcessing\Providers\TvdbProvider. Consider adding an additional type-check to rule them out.
Loading history...
41
    }
42
43
    protected function process(TvProcessingPassable $passable): TvProcessingResult
44
    {
45
        $parsedInfo = $passable->getParsedInfo();
46
        $context = $passable->context;
47
48
        if ($parsedInfo === null || empty($parsedInfo['cleanname'])) {
49
            return TvProcessingResult::notFound($this->getName());
50
        }
51
52
        $cleanName = $parsedInfo['cleanname'];
53
54
        // Check if we've already failed this title
55
        if ($this->isInTitleCache($cleanName)) {
56
            $this->outputSkipped($cleanName);
57
            return TvProcessingResult::skipped('previously failed', $this->getName());
58
        }
59
60
        $tvdb = $this->getTvdb();
61
        $siteId = false;
62
        $posterUrl = '';
63
64
        // Find the Video ID if it already exists by checking the title
65
        $videoId = $tvdb->getByTitle($cleanName, self::TYPE_TV);
66
67
        // If not found and cleanName contains a year in parentheses, try without the year
68
        if ($videoId === 0 && preg_match('/^(.+?)\s*\(\d{4}\)$/', $cleanName, $yearMatch)) {
69
            $nameWithoutYear = trim($yearMatch[1]);
70
            $videoId = $tvdb->getByTitle($nameWithoutYear, self::TYPE_TV);
71
        }
72
73
        if ($videoId !== 0) {
74
            $siteId = $tvdb->getSiteByID('tvdb', $videoId);
75
        }
76
77
        // Check if we have a valid country
78
        $country = (
79
            isset($parsedInfo['country']) && strlen($parsedInfo['country']) === 2
80
                ? (string) $parsedInfo['country']
81
                : ''
82
        );
83
84
        if ($siteId === false) {
85
            // Not in local DB, search TVDB
86
            $this->outputSearching($cleanName);
87
88
            $tvdbShow = $tvdb->getShowInfo((string) $cleanName);
89
90
            // If not found and cleanName contains a year in parentheses, try without the year
91
            if ($tvdbShow === false && preg_match('/^(.+?)\s*\(\d{4}\)$/', $cleanName, $yearMatch)) {
0 ignored issues
show
introduced by
The condition $tvdbShow === false is always false.
Loading history...
92
                $nameWithoutYear = trim($yearMatch[1]);
93
                $tvdbShow = $tvdb->getShowInfo($nameWithoutYear);
94
            }
95
96
            if (is_array($tvdbShow)) {
0 ignored issues
show
introduced by
The condition is_array($tvdbShow) is always true.
Loading history...
97
                $tvdbShow['country'] = $country;
98
                $videoId = $tvdb->add($tvdbShow);
99
                $siteId = (int) $tvdbShow['tvdb'];
100
                $posterUrl = $tvdbShow['poster'] ?? '';
101
            }
102
        } else {
103
            $this->outputFoundInDb($cleanName);
104
        }
105
106
        if ((int) $videoId === 0 || (int) $siteId === 0) {
107
            // Show not found
108
            $this->addToTitleCache($cleanName);
109
            $this->outputNotFound($cleanName);
110
            return TvProcessingResult::notFound($this->getName(), ['title' => $cleanName]);
111
        }
112
113
        // Fetch poster if available
114
        if (! empty($posterUrl)) {
115
            $tvdb->getPoster($videoId);
116
        } else {
117
            $this->fetchFanartPoster($videoId, $siteId);
118
        }
119
120
        // Process episode
121
        $seriesNo = ! empty($parsedInfo['season']) ? preg_replace('/^S0*/i', '', (string) $parsedInfo['season']) : '';
122
        $episodeNo = ! empty($parsedInfo['episode']) ? preg_replace('/^E0*/i', '', (string) $parsedInfo['episode']) : '';
123
        $hasAirdate = ! empty($parsedInfo['airdate']);
124
125
        if ($episodeNo === 'all') {
126
            // Full season release
127
            $tvdb->setVideoIdFound($videoId, $context->releaseId, 0);
128
            $this->outputFullSeason($cleanName);
129
            return TvProcessingResult::matched($videoId, 0, $this->getName(), ['full_season' => true]);
130
        }
131
132
        // Download all episodes if new show to reduce API/bandwidth usage
133
        if (! $tvdb->countEpsByVideoID($videoId)) {
134
            $tvdb->getEpisodeInfo($siteId, -1, -1, $videoId);
135
        }
136
137
        // Check if we have the episode for this video ID
138
        $episode = $tvdb->getBySeasonEp($videoId, $seriesNo, $episodeNo, $parsedInfo['airdate'] ?? '');
139
140
        if ($episode === false) {
141
            if ($seriesNo !== '' && $episodeNo !== '') {
142
                // Try to get episode from TVDB
143
                $tvdbEpisode = $tvdb->getEpisodeInfo($siteId, (int) $seriesNo, (int) $episodeNo, $videoId);
144
145
                if ($tvdbEpisode) {
0 ignored issues
show
introduced by
$tvdbEpisode is a non-empty array, thus is always true.
Loading history...
Bug Best Practice introduced by
The expression $tvdbEpisode of type array<string,integer|mixed|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
146
                    $episode = $tvdb->addEpisode($videoId, $tvdbEpisode);
147
                }
148
            }
149
150
            if ($episode === false && $hasAirdate) {
151
                // Refresh episode cache and attempt airdate match
152
                $tvdb->getEpisodeInfo($siteId, -1, -1, $videoId);
153
                $episode = $tvdb->getBySeasonEp($videoId, 0, 0, $parsedInfo['airdate']);
154
            }
155
        }
156
157
        if ($episode !== false && is_numeric($episode) && $episode > 0) {
158
            // Success!
159
            $tvdb->setVideoIdFound($videoId, $context->releaseId, $episode);
160
            $this->outputMatch(
161
                $cleanName,
162
                $seriesNo !== '' ? (int) $seriesNo : null,
163
                $episodeNo !== '' ? (int) $episodeNo : null,
164
                $hasAirdate ? $parsedInfo['airdate'] : null
165
            );
166
            return TvProcessingResult::matched($videoId, (int) $episode, $this->getName());
167
        }
168
169
        // Episode not found
170
        $tvdb->setVideoIdFound($videoId, $context->releaseId, 0);
171
172
        if ($this->echoOutput) {
173
            $this->colorCli->primaryOver('    → ');
174
            $this->colorCli->alternateOver($this->truncateTitle($cleanName));
175
            if ($hasAirdate) {
176
                $this->colorCli->primaryOver(' | ');
177
                $this->colorCli->warningOver($parsedInfo['airdate']);
178
            }
179
            $this->colorCli->primaryOver(' → ');
180
            $this->colorCli->warning('Episode not found');
181
        }
182
183
        return TvProcessingResult::notFound($this->getName(), [
184
            'video_id' => $videoId,
185
            'episode_not_found' => true,
186
        ]);
187
    }
188
189
    /**
190
     * Fetch poster from Fanart.tv.
191
     */
192
    private function fetchFanartPoster(int $videoId, int $siteId): void
193
    {
194
        if ($this->fanart === null) {
195
            $this->fanart = new FanartTvService();
196
        }
197
198
        if (! $this->fanart->isConfigured()) {
0 ignored issues
show
Bug introduced by
The method isConfigured() does not exist on null. ( Ignorable by Annotation )

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

198
        if (! $this->fanart->/** @scrutinizer ignore-call */ isConfigured()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
199
            return;
200
        }
201
202
        $posterUrl = $this->fanart->getBestTvPoster($siteId);
203
        if (! empty($posterUrl)) {
204
            $this->getTvdb()->posterUrl = $posterUrl;
205
            $this->getTvdb()->getPoster($videoId);
206
        }
207
    }
208
209
    /**
210
     * Output full season match message.
211
     */
212
    private function outputFullSeason(string $title): void
213
    {
214
        if (! $this->echoOutput) {
215
            return;
216
        }
217
218
        $this->colorCli->primaryOver('    → ');
219
        $this->colorCli->headerOver($this->truncateTitle($title));
220
        $this->colorCli->primaryOver(' → ');
221
        $this->colorCli->primary('Full Season matched');
222
    }
223
}
224
225