Add movie export levels

This commit is contained in:
JonnyWong16 2020-08-04 20:51:39 -07:00
parent 07a9bdbde3
commit 906aedd2f1
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
3 changed files with 141 additions and 29 deletions

View file

@ -33,8 +33,10 @@ DOCUMENTATION :: END
<select class="form-control" id="export_level_select" name="export_level_select"> <select class="form-control" id="export_level_select" name="export_level_select">
<option value="1">Level 1 - Basic Metadata</option> <option value="1">Level 1 - Basic Metadata</option>
<option value="2">Level 2 - Extended Metadata</option> <option value="2">Level 2 - Extended Metadata</option>
<option value="3">Level 3 - Basic Media Info</option> <option value="3">Level 3 - All Metadata</option>
<option value="4">Level 4 - Extended Media Info</option> <option value="4">Level 4 - Basic Media Info</option>
<option value="5">Level 5 - Extended Media Info</option>
<option value="6">Level 6 - All Media Info</option>
<option value="9">Level 9 - Everything</option> <option value="9">Level 9 - Everything</option>
<option value="-999">Custom</option> <option value="-999">Custom</option>
</select> </select>

View file

@ -23,7 +23,7 @@ import json
import os import os
import threading import threading
from functools import partial from functools import partial, reduce
from io import open from io import open
from multiprocessing.dummy import Pool as ThreadPool from multiprocessing.dummy import Pool as ThreadPool
@ -301,7 +301,7 @@ SHOW_ATTRS = {
'viewCount': None, 'viewCount': None,
'viewedLeafCount': None, 'viewedLeafCount': None,
'year': None, 'year': None,
'seasons': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type]) 'seasons': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type][0])
} }
SEASON_ATTRS = { SEASON_ATTRS = {
@ -336,7 +336,7 @@ SEASON_ATTRS = {
'userRating': None, 'userRating': None,
'viewCount': None, 'viewCount': None,
'viewedLeafCount': None, 'viewedLeafCount': None,
'episodes': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type]) 'episodes': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type][0])
} }
EPISODE_ATTRS = { EPISODE_ATTRS = {
@ -563,7 +563,7 @@ ARTIST_ATTRS = {
'updatedAt': helpers.datetime_to_iso, 'updatedAt': helpers.datetime_to_iso,
'userRating': None, 'userRating': None,
'viewCount': None, 'viewCount': None,
'albums': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type]) 'albums': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type][0])
} }
ALBUM_ATTRS = { ALBUM_ATTRS = {
@ -619,7 +619,7 @@ ALBUM_ATTRS = {
'userRating': None, 'userRating': None,
'viewCount': None, 'viewCount': None,
'viewedLeafCount': None, 'viewedLeafCount': None,
'tracks': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type]) 'tracks': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type][0])
} }
TRACK_ATTRS = { TRACK_ATTRS = {
@ -740,7 +740,7 @@ PHOTO_ALBUM_ATTRS = {
# For some reason photos needs to be first, # For some reason photos needs to be first,
# otherwise the photo album ratingKey gets # otherwise the photo album ratingKey gets
# clobbered by the first photo's ratingKey # clobbered by the first photo's ratingKey
'photos': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type]), 'photos': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type][0]),
'addedAt': helpers.datetime_to_iso, 'addedAt': helpers.datetime_to_iso,
'art': None, 'art': None,
'composite': None, 'composite': None,
@ -836,7 +836,7 @@ COLLECTION_ATTRS = {
'title': None, 'title': None,
'type': None, 'type': None,
'updatedAt': helpers.datetime_to_iso, 'updatedAt': helpers.datetime_to_iso,
'children': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type]) 'children': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type][0])
} }
PLAYLIST_ATTRS = { PLAYLIST_ATTRS = {
@ -854,27 +854,101 @@ PLAYLIST_ATTRS = {
'title': None, 'title': None,
'type': None, 'type': None,
'updatedAt': helpers.datetime_to_iso, 'updatedAt': helpers.datetime_to_iso,
'items': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type]) 'items': lambda e: helpers.get_attrs_to_dict(e, MEDIA_TYPES[e.type][0])
} }
MOVIE_LEVELS = {
1: [
'ratingKey', 'title', 'titleSort', 'originalTitle', 'originallyAvailableAt', 'year',
'rating', 'ratingImage', 'audienceRating', 'audienceRatingImage', 'userRating', 'contentRating',
'studio', 'tagline', 'summary', 'guid', 'duration', 'durationHuman', 'type'
],
2: [
'directors.tag', 'writers.tag', 'producers.tag', 'roles.tag', 'roles.role',
'countries.tag', 'genres.tag', 'collections.tag', 'labels.tag', 'fields.name', 'fields.locked'
],
3: [
'art', 'thumb', 'key', 'chapterSource',
'chapters.tag', 'chapters.index', 'chapters.start', 'chapters.end', 'chapters.thumb',
'updatedAt', 'lastViewedAt', 'viewCount'
],
4: [
'locations', 'media.aspectRatio', 'media.audioChannels', 'media.audioCodec', 'media.audioProfile',
'media.bitrate', 'media.container', 'media.duration', 'media.height', 'media.width',
'media.videoCodec', 'media.videoFrameRate', 'media.videoProfile', 'media.videoResolution',
'media.optimizedVersion'
],
5: [
'media.parts.accessible', 'media.parts.exists', 'media.parts.file', 'media.parts.duration',
'media.parts.container', 'media.parts.indexes', 'media.parts.size', 'media.parts.sizeHuman',
'media.parts.audioProfile', 'media.parts.videoProfile',
'media.parts.optimizedForStreaming', 'media.parts.deepAnalysisVersion'
],
6: [
'media.parts.videoStreams.codec', 'media.parts.videoStreams.bitrate',
'media.parts.videoStreams.language', 'media.parts.videoStreams.languageCode',
'media.parts.videoStreams.title', 'media.parts.videoStreams.displayTitle',
'media.parts.videoStreams.extendedDisplayTitle', 'media.parts.videoStreams.hdr',
'media.parts.videoStreams.bitDepth', 'media.parts.videoStreams.colorSpace',
'media.parts.videoStreams.frameRate', 'media.parts.videoStreams.level',
'media.parts.videoStreams.profile', 'media.parts.videoStreams.refFrames',
'media.parts.videoStreams.scanType', 'media.parts.videoStreams.default',
'media.parts.videoStreams.height', 'media.parts.videoStreams.width',
'media.parts.audioStreams.codec', 'media.parts.audioStreams.bitrate',
'media.parts.audioStreams.language', 'media.parts.audioStreams.languageCode',
'media.parts.audioStreams.title', 'media.parts.audioStreams.displayTitle',
'media.parts.audioStreams.extendedDisplayTitle', 'media.parts.audioStreams.bitDepth',
'media.parts.audioStreams.channels', 'media.parts.audioStreams.audioChannelLayout',
'media.parts.audioStreams.profile', 'media.parts.audioStreams.samplingRate',
'media.parts.audioStreams.default',
'media.parts.subtitleStreams.codec', 'media.parts.subtitleStreams.format',
'media.parts.subtitleStreams.language', 'media.parts.subtitleStreams.languageCode',
'media.parts.subtitleStreams.title', 'media.parts.subtitleStreams.displayTitle',
'media.parts.subtitleStreams.extendedDisplayTitle', 'media.parts.subtitleStreams.forced',
'media.parts.subtitleStreams.default'
]
}
SHOW_LEVELS = {}
SEASON_LEVELS = {}
EPISODE_LEVELS = {}
ARTIST_LEVELS = {}
ALBUM_LEVELS = {}
TRACK_LEVELS = {}
PHOTO_ALBUM_LEVELS = {}
PHOTO_LEVELS = {}
COLLECTION_LEVELS = {}
PLAYLIST_LEVELS = {}
MEDIA_TYPES = { MEDIA_TYPES = {
'movie': MOVIE_ATTRS, 'movie': (MOVIE_ATTRS, MOVIE_LEVELS),
'show': SHOW_ATTRS, 'show': (SHOW_ATTRS, SHOW_LEVELS),
'season': SEASON_ATTRS, 'season': (SEASON_ATTRS, SEASON_LEVELS),
'episode': EPISODE_ATTRS, 'episode': (EPISODE_ATTRS, EPISODE_LEVELS),
'artist': ARTIST_ATTRS, 'artist': (ARTIST_ATTRS, ARTIST_LEVELS),
'album': ALBUM_ATTRS, 'album': (ALBUM_ATTRS, ALBUM_LEVELS),
'track': TRACK_ATTRS, 'track': (TRACK_ATTRS, TRACK_LEVELS),
'photo album': PHOTO_ALBUM_ATTRS, 'photo album': (PHOTO_ALBUM_ATTRS, PHOTO_ALBUM_LEVELS),
'photo': PHOTO_ATTRS, 'photo': (PHOTO_ATTRS, PHOTO_LEVELS),
'collection': COLLECTION_ATTRS, 'collection': (COLLECTION_ATTRS, COLLECTION_LEVELS),
'playlist': PLAYLIST_ATTRS 'playlist': (PLAYLIST_ATTRS, PLAYLIST_LEVELS)
} }
def export(section_id=None, rating_key=None, file_format='json'): def export(section_id=None, rating_key=None, file_format='json', level=1):
timestamp = helpers.timestamp() timestamp = helpers.timestamp()
level = helpers.cast_to_int(level)
if not section_id and not rating_key: if not section_id and not rating_key:
logger.error("Tautulli Exporter :: Export called but no section_id or rating_key provided.") logger.error("Tautulli Exporter :: Export called but no section_id or rating_key provided.")
return return
@ -884,6 +958,9 @@ def export(section_id=None, rating_key=None, file_format='json'):
elif section_id and not str(section_id).isdigit(): elif section_id and not str(section_id).isdigit():
logger.error("Tautulli Exporter :: Export called with invalid section_id '%s'.", section_id) logger.error("Tautulli Exporter :: Export called with invalid section_id '%s'.", section_id)
return return
elif not level:
logger.error("Tautulli Exporter :: Export called with invalid level '%s'.", level)
return
elif file_format not in ('json', 'csv'): elif file_format not in ('json', 'csv'):
logger.error("Tautulli Exporter :: Export called but invalid file_format '%s' provided.", file_format) logger.error("Tautulli Exporter :: Export called but invalid file_format '%s' provided.", file_format)
return return
@ -891,7 +968,7 @@ def export(section_id=None, rating_key=None, file_format='json'):
plex = Plex(plexpy.CONFIG.PMS_URL, plexpy.CONFIG.PMS_TOKEN) plex = Plex(plexpy.CONFIG.PMS_URL, plexpy.CONFIG.PMS_TOKEN)
if rating_key: if rating_key:
logger.debug("Tautulli Exporter :: Export called with rating_key %s", rating_key) logger.debug("Tautulli Exporter :: Export called with rating_key %s, level %d", rating_key, level)
item = plex.get_item(helpers.cast_to_int(rating_key)) item = plex.get_item(helpers.cast_to_int(rating_key))
media_type = item.type media_type = item.type
@ -913,7 +990,7 @@ def export(section_id=None, rating_key=None, file_format='json'):
items = [item] items = [item]
elif section_id: elif section_id:
logger.debug("Tautulli Exporter :: Export called with section_id %s", section_id) logger.debug("Tautulli Exporter :: Export called with section_id %s, level %d", section_id, level)
library = plex.get_library(section_id) library = plex.get_library(section_id)
media_type = library.type media_type = library.type
@ -929,6 +1006,36 @@ def export(section_id=None, rating_key=None, file_format='json'):
logger.error("Tautulli Exporter :: Cannot export media type '%s'", media_type) logger.error("Tautulli Exporter :: Cannot export media type '%s'", media_type)
return return
media_attrs, level_attrs = MEDIA_TYPES[media_type]
if level == 9:
export_attrs = media_attrs
else:
if level not in level_attrs:
logger.error("Tautulli Exporter :: Export called with invalid level '%s'.", level)
return
export_attrs_set = set()
_levels = sorted(level_attrs.keys())
for _level in _levels[:_levels.index(level) + 1]:
export_attrs_set.update(level_attrs[_level])
export_attrs_list = []
for attr in export_attrs_set:
split_attr = attr.split('.')
try:
value = helpers.get_by_path(media_attrs, split_attr)
except KeyError:
logger.warn("Tautulli Exporter :: Unknown export attribute '%s', skipping...", attr)
continue
for _attr in reversed(split_attr):
value = {_attr: value}
export_attrs_list.append(value)
export_attrs = reduce(helpers.dict_merge, export_attrs_list)
filename = helpers.clean_filename(filename) filename = helpers.clean_filename(filename)
export_id = add_export(timestamp=timestamp, export_id = add_export(timestamp=timestamp,
@ -941,12 +1048,10 @@ def export(section_id=None, rating_key=None, file_format='json'):
logger.error("Tautulli Exporter :: Failed to export '%s'", filename) logger.error("Tautulli Exporter :: Failed to export '%s'", filename)
return return
attrs = MEDIA_TYPES[media_type]
threading.Thread(target=_real_export, threading.Thread(target=_real_export,
kwargs={'export_id': export_id, kwargs={'export_id': export_id,
'items': items, 'items': items,
'attrs': attrs, 'attrs': export_attrs,
'file_format': file_format, 'file_format': file_format,
'filename': filename}).start() 'filename': filename}).start()

View file

@ -6490,7 +6490,8 @@ class WebInterface(object):
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@addtoapi() @addtoapi()
def export_metadata(self, section_id=None, rating_key=None, file_format='json', **kwargs): def export_metadata(self, section_id=None, rating_key=None, file_format='json',
export_level=1, **kwargs):
""" Export library or media metadata to a file """ Export library or media metadata to a file
``` ```
@ -6500,6 +6501,7 @@ class WebInterface(object):
Optional parameters: Optional parameters:
file_format (str): 'json' (default) or 'csv' file_format (str): 'json' (default) or 'csv'
export_level (int): The level of metadata to export
Returns: Returns:
json: json:
@ -6508,7 +6510,10 @@ class WebInterface(object):
} }
``` ```
""" """
result = exporter.export(section_id=section_id, rating_key=rating_key, file_format=file_format) result = exporter.export(section_id=section_id,
rating_key=rating_key,
file_format=file_format,
level=export_level)
if result: if result:
return {'result': 'success', 'message': 'Metadata export has started.'} return {'result': 'success', 'message': 'Metadata export has started.'}