/
__init__.py
718 lines (612 loc) · 30 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
# -*- coding: utf-8 -*-
# standard imports
from datetime import datetime
import os
import sys
# plex debugging
try:
import plexhints # noqa: F401
except ImportError:
pass
else: # the code is running outside of Plex
from plexhints import plexhints_setup, update_sys_path
plexhints_setup() # read the plugin plist file and determine if plexhints should use elevated policy or not
update_sys_path() # when running outside plex, append the path
from plexhints.agent_kit import Agent, Media # agent kit
from plexhints.constant_kit import CACHE_1DAY # constant kit
from plexhints.decorator_kit import handler # decorator kit
from plexhints.extras_kit import InterviewObject, OtherObject, TrailerObject # extras kit
from plexhints.locale_kit import Locale # locale kit
from plexhints.log_kit import Log # log kit
from plexhints.model_kit import Movie # model kit
from plexhints.network_kit import HTTP # network kit
from plexhints.object_kit import MessageContainer, MetadataSearchResult, SearchResult # object kit
from plexhints.parse_kit import JSON # parse kit
from plexhints.prefs_kit import Prefs # prefs kit
from plexhints.proxy_kit import Proxy # proxy kit
from plexhints.resource_kit import Resource # resource kit
from plexhints.util_kit import String # util kit
# imports from Libraries\Shared
import requests
from typing import Optional
# local imports
if sys.version_info.major < 3:
from default_prefs import default_prefs
import helpers
import igdb_helpers
from platform_map import platform_map
else:
from .default_prefs import default_prefs
from . import helpers
from . import igdb_helpers
from .platform_map import platform_map
# create the plugin menu under applications
@handler(prefix='/applications/retroarcher', name='RetroArcher') # todo try different thumbs
def main():
# since plex removed menu's nothing else is needed here
pass
def ValidatePrefs():
# type: () -> MessageContainer
"""
Validate plug-in preferences.
This function is called when the user modifies their preferences. The developer can check the newly provided values
to ensure they are correct (e.g. attempting a login to validate a username and password), and optionally return a
``MessageContainer`` to display any error information to the user. See the archived Plex documentation
`Predefined functions
<https://web.archive.org/web/https://dev.plexapp.com/docs/channels/basics.html#predefined-functions>`_
for more information.
Returns
-------
MessageContainer
Success or Error message dependeing on results of validation.
Examples
--------
>>> ValidatePrefs()
...
"""
error_message = '' # start with a blank error message
for key in default_prefs:
try:
Prefs[key]
except KeyError:
Log.Critical("Setting '%s' missing from 'DefaultPrefs.json'" % key)
error_message += "Setting '%s' missing from 'DefaultPrefs.json'<br/>" % key
else:
# test all types except 'str_' as string cannot fail
if key.startswith('int_'):
try:
int(Prefs[key])
except ValueError:
Log.Error("Setting '%s' must be an integer; Value '%s'" % (key, Prefs[key]))
error_message += "Setting '%s' must be an integer; Value '%s'<br/>" % (key, Prefs[key])
elif key.startswith('bool_') or key.startswith('scanner_'):
if Prefs[key] is not True and Prefs[key] is not False:
Log.Error("Setting '%s' must be True or False; Value '%s'" % (key, Prefs[key]))
error_message += "Setting '%s' must be True or False; Value '%s'<br/>" % (key, Prefs[key])
if key.startswith('dir_'):
if Prefs[key]:
if not os.path.isdir(Prefs[key]):
Log.Error("Setting '%s' directory does not exist; Value '%s'" % (key, Prefs[key]))
error_message += "Setting '%s' directory does not exist; Value '%s'<br/>" % (key, Prefs[key])
else:
Log.Error("Setting '%s' directory is blank; Value '%s'" % (key, Prefs[key]))
error_message += "Setting '%s' directory is blank; Value '%s'<br/>" % (key, Prefs[key])
if key.startswith('url_'):
url = Prefs[key]
if url:
try:
status_code = requests.get(url).status_code
if status_code != 200:
Log.Error("Setting '%s' url returned a non 200 status code; Value '%s'" % (key, Prefs[key]))
error_message += "Setting '%s' url returned a non 200 status code; Value '%s'<br/>" % (
key, Prefs[key])
except Exception as e:
Log.Error("Setting '%s' url returned an exception; Exception '%s'" % (key, e))
error_message += "Setting '%s' url returned an exception; Exception '%s'<br/>" % (key, e)
else:
Log.Error("Setting '%s' url is blank; Value '%s'" % (key, Prefs[key]))
error_message += "Setting '%s' url is blank; Value '%s'<br/>" % (key, Prefs[key])
if error_message != '':
return MessageContainer(header='Error', message=error_message)
else:
Log.Info("DefaultPrefs.json is valid")
return MessageContainer(header='Success', message='RetroArcher - Provided preference values are ok')
def SetRating(key, rating):
# type: (str, float) -> None
"""
This function is called when the user sets the rating of a metadata item returned by the plug-in. The `key`
argument will be equal to the value of the item’s ``rating_key`` attribute. See the archived Plex documentation
`Predefined functions
<https://web.archive.org/web/https://dev.plexapp.com/docs/channels/basics.html#predefined-functions>`_
for more information.
Parameters
----------
key : str
This will be equal to the value of the item’s rating_key attribute.
rating : float
A float between 0 and 10 specifying the item’s rating.
Examples
--------
>>> SetRating(key='123456', rating=8.8)
...
"""
Log.Debug('User rated item (rating key: %s) with rating of %s' % (key, rating))
# todo - possibly rate the item on IGDB
def Start():
# type: () -> None
"""
Start the plug-in.
This function is called when the plug-in first starts. It can be used to perform extra initialisation tasks such as
configuring the environment and setting default attributes. See the archived Plex documentation
`Predefined functions
<https://web.archive.org/web/https://dev.plexapp.com/docs/channels/basics.html#predefined-functions>`_
for more information.
Examples
--------
>>> Start()
...
"""
# validate prefs
prefs_valid = ValidatePrefs()
if prefs_valid.header == 'Error':
Log.Warn('RetroArcher Metadata agent preferences are not valid.')
# set cache time
HTTP.CacheTime = CACHE_1DAY
Log.Debug('RetroArcher Metadata agent started.')
class RetroArcher(Agent.Movies):
"""
Class representing the RetroArcher Plex Movie Agent.
This class defines the metadata agent. See the archived Plex documentation
`Defining an agent class
<https://web.archive.org/web/https://dev.plexapp.com/docs/agents/basics.html#defining-an-agent-class>`_
for more information.
References
----------
name : str
A string defining the name of the agent for display in the GUI.
languages : list
A list of strings defining the languages supported by the agent. These values should be taken from the constants
defined in the `Locale
<https://web.archive.org/web/https://dev.plexapp.com/docs/api/localekit.html#module-Locale>`_
API.
primary_provider : bool
A boolean value defining whether the agent is a primary metadata provider or not. Primary providers can be
selected as the main source of metadata for a particular media type. If an agent is secondary
(``primary_provider`` is set to ``False``) it will only be able to contribute to data provided by another
primary agent.
fallback_agent : Optional[str]
A string containing the identifier of another agent to use as a fallback. If none of the matches returned by an
agent are a close enough match to the given set of hints, this fallback agent will be called to attempt to find
a better match.
accepts_from : Optional[list]
A list of strings containing the identifiers of agents that can contribute secondary data to primary data
provided by this agent.
contributes_to : Optional[list]
A list of strings containing the identifiers of primary agents that the agent can contribute secondary data to.
Methods
-------
search:
Search for an item.
update:
Add or update metadata for an item.
Examples
--------
>>> RetroArcher()
...
"""
name = 'RetroArcher'
languages = [
Locale.Language.English
]
primary_provider = True
fallback_agent = False
accepts_from = [
'com.plexapp.agents.localmedia'
]
contributes_to = None
@staticmethod
def search(results, media, lang, manual):
# type: (SearchResult, Media.Movie, str, bool) -> Optional[SearchResult]
"""
Search for an item.
When the media server needs an agent to perform a search, it calls the agent’s ``search`` method. See the
archived Plex documentation
`Searching for results to provide matches for media
<https://web.archive.org/web/https://dev.plexapp.com/docs/agents/search.html>`_
for more information.
Parameters
----------
results : SearchResult
An empty container that the developer should populate with potential matches.
media : Media.Movie
An object containing hints to be used when performing the search.
lang : str
A string identifying the user’s currently selected language. This will be one of the constants added to the
agent’s ``languages`` attribute.
manual : bool
A boolean value identifying whether the search was issued automatically during scanning, or manually by the
user (in order to fix an incorrect match).
Returns
-------
Optional[SearchResult]
The search result object, if the search was successful.
Examples
--------
>>> RetroArcher().search(results=..., media=..., lang='en', manual=True)
...
"""
Log.Debug('Searching with arguments: {results=%s, media=%s, lang=%s, manual=%s' %
(results, media, lang, manual))
# media.name example = 'Driver You Are the Wheelman Usa V1 1'
# media.title example = 'Driver - You Are the Wheelman (USA) (v1.1)'
if not media.title: # cannot search if no title
Log.Error('Cannot search since "media.title" is empty: %s' % media.title)
return
if media.title.startswith('clear-cache'):
Log.Info('Clearing HTTP cache.')
HTTP.ClearCache() # Clear Plex HTTP cache manually by searching a title named "clear-cache"
# get the fullpath and media filename
fullpath, media_filename = helpers.get_media_fullpath(media=media)
# get the game version
game_version = helpers.get_game_version(media_filename=media_filename)
# get the game platform
game_platform = helpers.get_game_platform(path=fullpath)
# get the game name
game_name = helpers.get_game_name(media=media, media_filename=media_filename)
# get the igdb id
platform_id = platform_map[game_platform]['systemIds']['igdb']
# download platform data (including list of all games for that platform)
# use plex JSON kit since they handle cache automatically
platform_data = JSON.ObjectFromURL(url='https://db.lizardbyte.dev/platforms/%s.json' % platform_id)
for game in platform_data['games']:
result_name = game['name'].encode('utf-8')
result_year = None
try:
for release_date in game['release_dates']:
try:
if release_date['platform'] == platform_id and\
(result_year is None or release_date['y'] < result_year):
result_year = release_date['y']
except KeyError:
Log.Info('Game {%s} is missing release year for one or more regions on platform {%s}. '
'Contribute at https://www.igdb.com' % (result_name, game_platform))
except KeyError:
Log.Info('Game {%s} is missing release dates on platform {%s}. Contribute at https://www.igdb.com' %
(result_name, game_platform))
result_score = int(String.LevenshteinRatio(first=game_name, second=result_name) * 100)
try:
# https://api-docs.igdb.com/#images
result_thumb = 'https:%s' % game['cover']['url'].replace('/t_thumb/', '/t_cover_big/')
except KeyError:
result_thumb = None
results.Append(MetadataSearchResult(
id='{igdb-%s}{platform-%s}{%s}' % (game['id'], platform_id, game_version),
# need many variables to build this in the update function
# cannot do the following or all versions with igdb id get combined to a single entry
# id = 'igdb-%s' % game['id],
name=result_name,
year=result_year,
score=result_score,
lang=lang, # no lang to get from db
thumb=result_thumb
))
# sort the results first by year, then by score
results.Sort(attr='year')
results.Sort(attr='score', descending=True)
return results
@staticmethod
def update(metadata, media, lang, force):
# type: (Movie, Media.Movie, str, bool) -> Optional[Movie]
"""
Update metadata for an item.
Once an item has been successfully matched, it is added to the update queue. As the framework processes queued
items, it calls the ``update`` method of the relevant agents. See the archived Plex documentation
`Adding metadata to media
<https://web.archive.org/web/https://dev.plexapp.com/docs/agents/update.html>`_
for more information.
Parameters
----------
metadata : object
A pre-initialized metadata object if this is the first time the item is being updated, or the existing
metadata object if the item is being refreshed.
media : object
An object containing information about the media hierarchy in the database.
lang : str
A string identifying which language should be used for the metadata. This will be one of the constants
defined in the agent’s ``languages`` attribute.
force : bool
A boolean value identifying whether the user forced a full refresh of the metadata. If this argument is
``True``, all metadata should be refreshed, regardless of whether it has been populated previously.
Examples
--------
>>> RetroArcher().update(metadata=..., media=..., lang='en', force=True)
...
"""
Log.Debug('Updating with arguments: {metadata=%s, media=%s, lang=%s, force=%s' %
(metadata, media, lang, force))
# parameters
id_list = helpers.get_list_of_substrings(string_subject=metadata.id, string1='{', string2='}')
if 'igdb-' not in id_list[0] or 'platform-' not in id_list[1]:
Log.Critical('This item has a problem with the id, please rematch the item to correct the issue. Exiting.')
return
igdb_id = int(id_list[0].split('-', 1)[-1])
igdb_platform_id = int(id_list[1].split('-', 1)[-1])
game_version = id_list[2]
Log.Info('IGDB id: %s' % igdb_id)
Log.Info('IGDB platform-id: %s' % igdb_platform_id)
Log.Info('Game version: %s' % game_version)
# get the platform_name from the platform_id
platform_name = None
for platform, platform_data in platform_map.items():
if platform_data['systemIds']['igdb'] == igdb_platform_id:
platform_name = platform
Log.Info('Game platform: %s' % platform_name)
break
if not platform_name: # platform not found
Log.Critical('Platform name not found. Exiting.')
return
# download game data
# use plex JSON kit since they handle cache automatically
game = JSON.ObjectFromURL(url='https://db.lizardbyte.dev/games/%s.json' % igdb_id)
# setup missing data list
missing_details = []
# title
title = "%s [%s] %s" % (game['name'], platform_name, game_version)
metadata.title = title.strip()
Log.Info('Title: %s' % metadata.title)
# summary
try:
metadata.summary = game['summary']
Log.Info('Summary: %s' % metadata.summary)
except KeyError:
metadata.summary = None
missing_details.append('summary')
# critic rating
try:
metadata.rating = game['aggregated_rating'] / 10 # critic rating
if metadata.rating >= 5.0:
rating_image = 'rating_up.png'
else:
rating_image = 'rating_down.png'
# todo - image doesn't work
# metadata.rating_image = '/:/plugins/dev.lizardbyte.retroarcher-plex/resources/%s' % rating_image
# metadata.rating_image = R(rating_image)
metadata.rating_image = Resource.ExternalPath(rating_image)
Log.Info('Rating: %s' % metadata.rating)
except KeyError:
metadata.rating = None
metadata.rating_image = None
missing_details.append('aggregated_rating')
# audience rating
try:
metadata.audience_rating = game['rating'] / 10 # audience rating
if metadata.audience_rating >= 5.0:
rating_image = 'rating_up.png'
else:
rating_image = 'rating_down.png'
# todo - image doesn't work
# metadata.audience_rating_image = '/:/plugins/dev.lizardbyte.retroarcher-plex/resources/%s' % rating_image
# metadata.audience_rating_image = R(rating_image)
metadata.audience_rating_image = Resource.ExternalPath(rating_image)
Log.Info('Audience rating: %s' % metadata.audience_rating)
except KeyError:
metadata.audience_rating = None
metadata.audience_rating_image = None
missing_details.append('rating')
# studio
try:
for company in game['involved_companies']:
if company['developer'] is True:
try:
metadata.studio = company['company']['name']
Log.Info('Studio: %s' % metadata.studio)
break
except KeyError:
missing_details.append('involved_companies/company_name')
except KeyError:
metadata.studio = None
missing_details.append('involved_companies')
# release_date
# todo - add a way to get release date for specific version of game... for now just use earliest release date
try:
date = None
for release_date in game['release_dates']:
if release_date['platform'] == igdb_platform_id:
if date:
if release_date['date'] < date: # use the earliest release date on that platform
metadata.year = release_date['y']
date = release_date['date']
else:
metadata.year = release_date['y']
date = release_date['date']
metadata.originally_available_at = datetime.utcfromtimestamp(date)
if not date:
try:
metadata.originally_available_at = datetime.utcfromtimestamp(game['first_release_date'])
metadata.year = datetime.utcfromtimestamp(game['first_release_date']).year
except KeyError:
missing_details.append('first_release_date')
Log.Info('Year: %s' % metadata.year)
Log.Info('Originally available at: %s' % metadata.originally_available_at)
except KeyError:
metadata.year = None
metadata.originally_available_at = None
missing_details.append('release_dates/platform')
# age ratings
# use plex JSON kit since they handle cache automatically
age_ratings_enums = JSON.ObjectFromURL(url='https://db.lizardbyte.dev/enums/age_ratings.json')
preferred_age_rating_category_id = 1 # set default to ESRB
for key, category in age_ratings_enums['category'].items():
if category == Prefs['enum_PreferredRatingSystem']:
preferred_age_rating_category_id = int(key)
rating_test_order = range(1, len(age_ratings_enums['category'])) # test in IGDB order (ESRB, PEGI, etc, etc.)
rating_test_order.remove(preferred_age_rating_category_id) # remove the preferred rating category
rating_test_order.insert(0, preferred_age_rating_category_id) # add the preferred rating category at the start
try:
found_rating = False
for category_id in rating_test_order:
if found_rating:
break
for age_rating in game['age_ratings']:
metadata.content_rating = age_ratings_enums['rating'][str(age_rating['rating'])]
metadata.content_rating_age = age_ratings_enums['rating_age'][str(age_rating['rating'])]
if age_rating['category'] == category_id:
found_rating = True # break out of the upper loop
Log.Info('Content rating: %s' % metadata.content_rating)
Log.Info('Content rating age: %s' % metadata.content_rating_age)
break # break out of the lower loop
except KeyError:
metadata.content_rating = None
metadata.content_rating_age = None
missing_details.append('age_ratings')
# posters
try:
poster_image = 'https:%s' % game['cover']['url'].replace('/t_thumb/', '/t_original/')
metadata.posters[id_list[0]] = Proxy.Media(HTTP.Request(poster_image), sort_order=0)
Log.Info('Setting poster image to: %s' % poster_image)
except KeyError:
missing_details.append('cover')
# art
art_keys = ['artworks', 'screenshots'] # add both artworks and screenshots to metadata.arts
art_index = 0
for key in art_keys:
try:
for artwork in game[key]:
art_image = 'https:%s' % artwork['url'].replace('/t_thumb/', '/t_original/')
metadata.art[art_image] = Proxy.Media(HTTP.Request(art_image), sort_order=art_index)
Log.Info('Adding art image: %s' % art_image)
art_index += 1
except KeyError:
missing_details.append(key)
# genres
metadata.genres.clear()
genre_keys = [
('Genre', 'genres'),
('Theme', 'themes'),
('Game Mode', 'game_modes'),
('Player Perspective', 'player_perspectives')
]
for key in genre_keys:
try:
for genre in game[key[1]]:
metadata.genres.add('%s: %s' % (key[0], genre['name']))
except KeyError:
missing_details.append(key[1])
# genres (multiplayer modes)
try:
for version in game['multiplayer_modes']: # this is a list
if version['platform'] == igdb_platform_id:
for mode in version: # this is a dict
try:
if version[mode] is False:
pass # skip these
elif version[mode] is True:
metadata.genres.add('Multiplayer Mode: %s' % igdb_helpers.multiplayer_mode[mode])
elif version[mode] > 0:
metadata.genres.add('Multiplayer Mode: %s: %s' %
(igdb_helpers.multiplayer_mode[mode], version[mode]))
except KeyError:
pass # skip any items not in our dictionary
except KeyError:
missing_details.append('multiplayer_modes')
# genres (platform)
metadata.genres.add("Platform: %s" % platform_name)
Log.Info('Genres: %s' % metadata.genres)
# collections
metadata.collections.clear()
try:
metadata.collections.add(game['collection']['name'])
except KeyError:
missing_details.append('collection')
# collections (franchises)
try:
for franchise in game['franchises']: # this is a list
if franchise['name'] not in metadata.collections:
metadata.collections.add(franchise['name'])
except KeyError:
missing_details.append('franchises')
# collection (platform)
if Prefs['bool_PlatformAsCollection'] is True:
metadata.collections.add("Platform: %s" % platform_name)
Log.Info('Collections: %s' % metadata.collections)
# extras / videos
extra_type_map = {
'trailer': TrailerObject,
'teaser': TrailerObject,
'gameplay': OtherObject,
'interview': InterviewObject
}
# todo - clear existing extra objects
try:
for video in game['videos']:
igdb_video_name = video['name']
video_data = JSON.ObjectFromURL(url='https://db.lizardbyte.dev/videos/%s.json' % video['video_id'])
video_url = 'https://www.youtube.com/watch?v=%s' % video_data['id']
video_title = video_data['snippet']['title']
video_thumbs = video_data['snippet']['thumbnails']
# iterate over a copy of the original dictionary, while modifying the original
# https://stackoverflow.com/a/33815594
for thumb_key, thumb_data in dict(video_thumbs).items():
if thumb_data is None:
del video_thumbs[thumb_key]
# sort the thumbs by size
# https://www.geeksforgeeks.org/python-sort-nested-dictionary-by-key/
video_thumbs = sorted(video_data['snippet']['thumbnails'].items(), key=lambda x: x[1]['width'],
reverse=True)
video_thumb = video_thumbs[0][-1]['url']
extra_method = OtherObject # set the default type, then try to match a better type
for extra_type in extra_type_map:
if extra_type in igdb_video_name.lower() or extra_type in video_title.lower():
extra_method = extra_type_map[extra_type]
break
metadata.extras.add(extra_method(title=video_title, url=video_url, thumb=video_thumb))
Log.Info('Adding extra: %s' % video_title)
Log.Info('Extra video url: %s' % video_url)
except KeyError:
missing_details.append('videos')
# actors (characters)
character_enums = JSON.ObjectFromURL(url='https://db.lizardbyte.dev/enums/characters.json')
metadata.roles.clear()
images_first = [True, False]
try:
for image_order in images_first:
for character in game['characters']:
try:
character['mug_shot']['url']
except KeyError:
has_image = False
else:
has_image = True
if has_image == image_order:
# create the role object
role = metadata.roles.new()
role.name = character['name']
role.photo = None # reset the image
try:
gender = character_enums['gender'][str(character['gender'])]
except KeyError:
gender = None
try:
species = character_enums['species'][str(character['species'])]
except KeyError:
species = None
if gender and species:
role.role = '%s | %s' % (gender, species)
elif gender:
role.role = '%s' % gender
elif species:
role.role = '%s' % species
Log.Info('Adding character named "%s" as "%s"' % (role.name, role.role))
if has_image:
role.photo = 'https:%s' % character['mug_shot']['url'].replace('/t_thumb/', '/t_original/')
Log.Info('Set character image to: %s' % role.photo)
except KeyError:
missing_details.append('characters')
# clear these
metadata.directors.clear()
metadata.producers.clear()
# log the missing data
if missing_details:
Log.Info('Game {%s} is missing the following metadata: %s. '
'Contribute at https://www.igdb.com' % (metadata.title, missing_details))
return metadata