Passed
Push — master ( 8b334f...d95312 )
by Darko
11:24
created

TmdbPipe::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\TvProcessing\TvProcessingPassable;
6
use App\Services\TvProcessing\TvProcessingResult;
7
use App\Services\TvProcessing\Providers\TmdbProvider;
8
9
/**
10
 * Pipe for TMDB API lookups.
11
 */
12
class TmdbPipe extends AbstractTvProviderPipe
13
{
14
    // Video type and source constants (matching Videos class protected constants)
15
    private const TYPE_TV = 0;
16
    private const SOURCE_TMDB = 2;
17
18
    protected int $priority = 40;
19
    private ?TmdbProvider $tmdb = null;
20
21
    public function getName(): string
22
    {
23
        return 'TMDB';
24
    }
25
26
    public function getStatusCode(): int
27
    {
28
        return -2; // PROCESS_TMDB
29
    }
30
31
    /**
32
     * Get or create the TMDB instance.
33
     */
34
    private function getTmdb(): TmdbProvider
35
    {
36
        if ($this->tmdb === null) {
37
            $this->tmdb = new TmdbProvider();
38
        }
39
        return $this->tmdb;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->tmdb could return the type null which is incompatible with the type-hinted return App\Services\TvProcessing\Providers\TmdbProvider. Consider adding an additional type-check to rule them out.
Loading history...
40
    }
41
42
    protected function process(TvProcessingPassable $passable): TvProcessingResult
43
    {
44
        $parsedInfo = $passable->getParsedInfo();
45
        $context = $passable->context;
46
47
        if ($parsedInfo === null || empty($parsedInfo['cleanname'])) {
48
            return TvProcessingResult::notFound($this->getName());
49
        }
50
51
        $cleanName = $parsedInfo['cleanname'];
52
53
        // Check if we've already failed this title
54
        if ($this->isInTitleCache($cleanName)) {
55
            $this->outputSkipped($cleanName);
56
            return TvProcessingResult::skipped('previously failed', $this->getName());
57
        }
58
59
        $tmdb = $this->getTmdb();
60
        $siteId = false;
61
62
        // Find the Video ID if it already exists
63
        $videoId = $tmdb->getByTitle($cleanName, self::TYPE_TV, self::SOURCE_TMDB);
64
65
        // If not found and cleanName contains a year in parentheses, try without the year
66
        if ($videoId === 0 && preg_match('/^(.+?)\s*\(\d{4}\)$/', $cleanName, $yearMatch)) {
67
            $nameWithoutYear = trim($yearMatch[1]);
68
            $videoId = $tmdb->getByTitle($nameWithoutYear, self::TYPE_TV, self::SOURCE_TMDB);
69
        }
70
71
        if ($videoId !== 0) {
72
            $siteId = $tmdb->getSiteByID('tmdb', $videoId);
73
            // If show exists in local DB with a TMDB ID, use it directly
74
            if ($siteId !== false && $siteId !== 0) {
75
                $this->outputFoundInDb($cleanName);
76
            } else {
77
                // Show exists in local DB but without TMDB ID (from another source)
78
                // Skip TMDB API search and proceed to episode matching
79
                $this->outputFoundInDb($cleanName);
80
                return $this->processEpisodeForExistingVideo($passable, $tmdb, $videoId, $parsedInfo);
81
            }
82
        }
83
84
        if ($videoId === 0) {
85
            // Not in local DB, search TMDB
86
            $this->outputSearching($cleanName);
87
88
            $tmdbShow = $tmdb->getShowInfo((string) $cleanName);
89
90
            // If not found and cleanName contains a year in parentheses, try without the year
91
            if ($tmdbShow === false && preg_match('/^(.+?)\s*\(\d{4}\)$/', $cleanName, $yearMatch)) {
0 ignored issues
show
introduced by
The condition $tmdbShow === false is always false.
Loading history...
92
                $nameWithoutYear = trim($yearMatch[1]);
93
                $tmdbShow = $tmdb->getShowInfo($nameWithoutYear);
94
            }
95
96
            if (is_array($tmdbShow)) {
0 ignored issues
show
introduced by
The condition is_array($tmdbShow) is always true.
Loading history...
97
                // Check if we have a valid country
98
                if (isset($parsedInfo['country']) && strlen($parsedInfo['country']) === 2) {
99
                    $tmdbShow['country'] = $parsedInfo['country'];
100
                }
101
                $videoId = $tmdb->add($tmdbShow);
102
                $siteId = (int) $tmdbShow['tmdb'];
103
            }
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 we have one
114
        if (! empty($tmdbShow['poster'] ?? '')) {
115
            $tmdb->getPoster($videoId);
116
        }
117
118
        // Process episode
119
        $seriesNo = ! empty($parsedInfo['season']) ? preg_replace('/^S0*/i', '', (string) $parsedInfo['season']) : '';
120
        $episodeNo = ! empty($parsedInfo['episode']) ? preg_replace('/^E0*/i', '', (string) $parsedInfo['episode']) : '';
121
        $hasAirdate = ! empty($parsedInfo['airdate']);
122
123
        if ($episodeNo === 'all') {
124
            // Full season release
125
            $tmdb->setVideoIdFound($videoId, $context->releaseId, 0);
126
            $this->outputFullSeason($cleanName);
127
            return TvProcessingResult::matched($videoId, 0, $this->getName(), ['full_season' => true]);
128
        }
129
130
        // Download all episodes if new show to reduce API/bandwidth usage
131
        if (! $tmdb->countEpsByVideoID($videoId)) {
132
            $tmdb->getEpisodeInfo($siteId, -1, -1);
133
        }
134
135
        // Check if we have the episode for this video ID
136
        $episode = $tmdb->getBySeasonEp($videoId, $seriesNo, $episodeNo, $parsedInfo['airdate'] ?? '');
137
138
        if ($episode === false) {
139
            if ($seriesNo !== '' && $episodeNo !== '') {
140
                // Try to get episode from TMDB
141
                $tmdbEpisode = $tmdb->getEpisodeInfo($siteId, (int) $seriesNo, (int) $episodeNo);
142
143
                if ($tmdbEpisode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tmdbEpisode of type array<string,integer|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...
introduced by
$tmdbEpisode is a non-empty array, thus is always true.
Loading history...
144
                    $episode = $tmdb->addEpisode($videoId, $tmdbEpisode);
145
                }
146
            }
147
148
            if ($episode === false && $hasAirdate) {
149
                // Refresh episode cache and attempt airdate match
150
                $tmdb->getEpisodeInfo($siteId, -1, -1);
151
                $episode = $tmdb->getBySeasonEp($videoId, 0, 0, $parsedInfo['airdate']);
152
            }
153
        }
154
155
        if ($episode !== false && is_numeric($episode) && $episode > 0) {
156
            // Success!
157
            $tmdb->setVideoIdFound($videoId, $context->releaseId, $episode);
158
            $this->outputMatch(
159
                $cleanName,
160
                $seriesNo !== '' ? (int) $seriesNo : null,
161
                $episodeNo !== '' ? (int) $episodeNo : null,
162
                $hasAirdate ? $parsedInfo['airdate'] : null
163
            );
164
            return TvProcessingResult::matched($videoId, (int) $episode, $this->getName());
165
        }
166
167
        // Episode not found
168
        $tmdb->setVideoIdFound($videoId, $context->releaseId, 0);
169
170
        if ($this->echoOutput) {
171
            $this->colorCli->primaryOver('    → ');
172
            $this->colorCli->alternateOver($this->truncateTitle($cleanName));
173
            if ($hasAirdate) {
174
                $this->colorCli->primaryOver(' | ');
175
                $this->colorCli->warningOver($parsedInfo['airdate']);
176
            }
177
            $this->colorCli->primaryOver(' → ');
178
            $this->colorCli->warning('Episode not found');
179
        }
180
181
        return TvProcessingResult::notFound($this->getName(), [
182
            'video_id' => $videoId,
183
            'episode_not_found' => true,
184
        ]);
185
    }
186
187
    /**
188
     * Output full season match message.
189
     */
190
    private function outputFullSeason(string $title): void
191
    {
192
        if (! $this->echoOutput) {
193
            return;
194
        }
195
196
        $this->colorCli->primaryOver('    → ');
197
        $this->colorCli->headerOver($this->truncateTitle($title));
198
        $this->colorCli->primaryOver(' → ');
199
        $this->colorCli->primary('Full Season matched');
200
    }
201
202
    /**
203
     * Process episode matching for a video that already exists in local DB.
204
     * This is used when the show was added from another source and doesn't have a TMDB ID.
205
     */
206
    private function processEpisodeForExistingVideo(
207
        TvProcessingPassable $passable,
208
        TmdbProvider $tmdb,
209
        int $videoId,
210
        array $parsedInfo
211
    ): TvProcessingResult {
212
        $context = $passable->context;
213
        $cleanName = $parsedInfo['cleanname'];
214
215
        $seriesNo = ! empty($parsedInfo['season']) ? preg_replace('/^S0*/i', '', (string) $parsedInfo['season']) : '';
216
        $episodeNo = ! empty($parsedInfo['episode']) ? preg_replace('/^E0*/i', '', (string) $parsedInfo['episode']) : '';
217
        $hasAirdate = ! empty($parsedInfo['airdate']);
218
219
        if ($episodeNo === 'all') {
220
            // Full season release
221
            $tmdb->setVideoIdFound($videoId, $context->releaseId, 0);
222
            $this->outputFullSeason($cleanName);
223
            return TvProcessingResult::matched($videoId, 0, $this->getName(), ['full_season' => true]);
224
        }
225
226
        // Try to find episode in local DB
227
        $episode = $tmdb->getBySeasonEp($videoId, $seriesNo, $episodeNo, $parsedInfo['airdate'] ?? '');
228
229
        if ($episode !== false && is_numeric($episode) && $episode > 0) {
230
            $tmdb->setVideoIdFound($videoId, $context->releaseId, $episode);
231
            $this->outputMatch(
232
                $cleanName,
233
                $seriesNo !== '' ? (int) $seriesNo : null,
234
                $episodeNo !== '' ? (int) $episodeNo : null,
235
                $hasAirdate ? $parsedInfo['airdate'] : null
236
            );
237
            return TvProcessingResult::matched($videoId, (int) $episode, $this->getName());
238
        }
239
240
        // Episode not found in local DB - mark video but episode not matched
241
        $tmdb->setVideoIdFound($videoId, $context->releaseId, 0);
242
243
        if ($this->echoOutput) {
244
            $this->colorCli->primaryOver('    → ');
245
            $this->colorCli->alternateOver($this->truncateTitle($cleanName));
246
            if ($hasAirdate) {
247
                $this->colorCli->primaryOver(' | ');
248
                $this->colorCli->warningOver($parsedInfo['airdate']);
249
            }
250
            $this->colorCli->primaryOver(' → ');
251
            $this->colorCli->warning('Episode not in local DB');
252
        }
253
254
        return TvProcessingResult::notFound($this->getName(), [
255
            'video_id' => $videoId,
256
            'episode_not_found' => true,
257
        ]);
258
    }
259
}
260