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); |
|
|
|
|
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: '). |
|
|
|
|
110
|
|
|
$this->colorCli->warningOver($release['cleanname']). |
|
|
|
|
111
|
|
|
$this->colorCli->header(' already failed lookup for this site. Skipping.', true); |
|
|
|
|
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: '). |
|
|
|
|
129
|
|
|
$this->colorCli->headerOver($release['cleanname']). |
|
|
|
|
130
|
|
|
$this->colorCli->primary('.', true); |
|
|
|
|
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); |
|
|
|
|
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; |
|
|
|
|
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')) { |
|
|
|
|
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')) { |
|
|
|
|
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
|
|
|
|