Movie Sync Alpha version
This commit is contained in:
parent
488c1a0a19
commit
88f649d36d
4 changed files with 620 additions and 97 deletions
|
@ -50,6 +50,8 @@ from threading import Thread
|
||||||
import Queue
|
import Queue
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import xml.etree.cElementTree as etree
|
import xml.etree.cElementTree as etree
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -85,6 +87,8 @@ class PlexAPI():
|
||||||
self.plexversion = client.getVersion()
|
self.plexversion = client.getVersion()
|
||||||
self.platform = client.getPlatform()
|
self.platform = client.getPlatform()
|
||||||
|
|
||||||
|
self.doUtils = downloadutils.DownloadUtils()
|
||||||
|
|
||||||
def logMsg(self, msg, lvl=1):
|
def logMsg(self, msg, lvl=1):
|
||||||
className = self.__class__.__name__
|
className = self.__class__.__name__
|
||||||
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||||
|
@ -156,7 +160,7 @@ class PlexAPI():
|
||||||
url = url + '/clients'
|
url = url + '/clients'
|
||||||
self.logMsg("CheckConnection called for url %s with a token" % url, 2)
|
self.logMsg("CheckConnection called for url %s with a token" % url, 2)
|
||||||
|
|
||||||
r = downloadutils.DownloadUtils().downloadUrl(
|
r = self.doUtils.downloadUrl(
|
||||||
url,
|
url,
|
||||||
authenticate=False,
|
authenticate=False,
|
||||||
headerOptions={'X-Plex-Token': token}
|
headerOptions={'X-Plex-Token': token}
|
||||||
|
@ -1233,33 +1237,467 @@ class PlexAPI():
|
||||||
})
|
})
|
||||||
return serverlist
|
return serverlist
|
||||||
|
|
||||||
def GetPlexCollections(self, type):
|
def GetPlexCollections(self, mediatype):
|
||||||
# Build a list of the user views
|
"""
|
||||||
|
Input:
|
||||||
|
mediatype String or list of strings with possible values
|
||||||
|
'movie', 'show', 'artist', 'photo'
|
||||||
|
Output:
|
||||||
|
Collection containing only mediatype. List with entry of the form:
|
||||||
|
{
|
||||||
|
'name': xxx Plex title for the media section
|
||||||
|
'type': xxx Plex type: 'movie', 'show', 'artist', 'photo'
|
||||||
|
'id': xxx Plex unique key for the section
|
||||||
|
}
|
||||||
|
"""
|
||||||
collections = []
|
collections = []
|
||||||
|
|
||||||
url = "{server}/library/sections"
|
url = "{server}/library/sections"
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
jsondata = self.doUtils.downloadUrl(url)
|
||||||
try:
|
try:
|
||||||
result = jsondata['_children']
|
result = jsondata['_children']
|
||||||
except:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
for item in result:
|
for item in result:
|
||||||
|
|
||||||
# name = item['Name']
|
|
||||||
name = item['title']
|
|
||||||
# contentType = item['Type']
|
|
||||||
contentType = item['type']
|
contentType = item['type']
|
||||||
itemtype = contentType
|
if contentType in mediatype:
|
||||||
content = itemtype
|
name = item['title']
|
||||||
contentID = item['key']
|
contentId = item['key']
|
||||||
|
|
||||||
if itemtype == type and name not in ("Collections", "Trailers"):
|
|
||||||
collections.append({
|
collections.append({
|
||||||
|
'name': name,
|
||||||
'title': name,
|
'type': contentType,
|
||||||
'type': itemtype,
|
'id': str(contentId)
|
||||||
'id': contentID,
|
|
||||||
'content': content
|
|
||||||
})
|
})
|
||||||
return collections
|
return collections
|
||||||
|
|
||||||
|
def GetPlexSectionResults(self, viewId):
|
||||||
|
"""
|
||||||
|
Returns a list (raw API dump) of all Plex movies in the Plex section
|
||||||
|
with key = viewId.
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
url = "{server}/library/sections/%s/all" % viewId
|
||||||
|
jsondata = self.doUtils.downloadUrl(url)
|
||||||
|
try:
|
||||||
|
result = jsondata['_children']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return result
|
||||||
|
|
||||||
|
def GetPlexMetadata(self, key):
|
||||||
|
"""
|
||||||
|
Returns raw API metadata for key.
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
url = "{server}" + key
|
||||||
|
jsondata = self.doUtils.downloadUrl(url)
|
||||||
|
try:
|
||||||
|
result = jsondata['_children'][0]
|
||||||
|
except KeyError:
|
||||||
|
self.logMsg("Error retrieving metadata for %s" % url, 1)
|
||||||
|
pass
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class API():
|
||||||
|
|
||||||
|
def __init__(self, item):
|
||||||
|
|
||||||
|
self.item = item
|
||||||
|
self.clientinfo = clientinfo.ClientInfo()
|
||||||
|
self.addonName = self.clientinfo.getAddonName()
|
||||||
|
|
||||||
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
|
className = self.__class__.__name__
|
||||||
|
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||||
|
|
||||||
|
def convert_date(self, stamp):
|
||||||
|
"""
|
||||||
|
convert_date(stamp) converts a Unix time stamp (seconds passed since
|
||||||
|
January 1 1970) to a propper, human-readable time stamp
|
||||||
|
"""
|
||||||
|
# DATEFORMAT = xbmc.getRegion('dateshort')
|
||||||
|
# TIMEFORMAT = xbmc.getRegion('meridiem')
|
||||||
|
# date_time = time.localtime(stamp)
|
||||||
|
# if DATEFORMAT[1] == 'd':
|
||||||
|
# localdate = time.strftime('%d-%m-%Y', date_time)
|
||||||
|
# elif DATEFORMAT[1] == 'm':
|
||||||
|
# localdate = time.strftime('%m-%d-%Y', date_time)
|
||||||
|
# else:
|
||||||
|
# localdate = time.strftime('%Y-%m-%d', date_time)
|
||||||
|
# if TIMEFORMAT != '/':
|
||||||
|
# localtime = time.strftime('%I:%M%p', date_time)
|
||||||
|
# else:
|
||||||
|
# localtime = time.strftime('%H:%M', date_time)
|
||||||
|
# return localtime + ' ' + localdate
|
||||||
|
DATEFORMAT = xbmc.getRegion('dateshort')
|
||||||
|
TIMEFORMAT = xbmc.getRegion('meridiem')
|
||||||
|
date_time = time.localtime(stamp)
|
||||||
|
localdate = time.strftime('%Y-%m-%d', date_time)
|
||||||
|
return localdate
|
||||||
|
|
||||||
|
def getChecksum(self):
|
||||||
|
"""
|
||||||
|
Maybe get rid of viewOffset = (resume point)?!?
|
||||||
|
"""
|
||||||
|
item = self.item
|
||||||
|
checksum = "%s%s%s%s%s" % (
|
||||||
|
item['key'],
|
||||||
|
item['updatedAt'],
|
||||||
|
item.get('viewCount', ""),
|
||||||
|
item.get('lastViewedAt', ""),
|
||||||
|
item.get('viewOffset', "")
|
||||||
|
)
|
||||||
|
return checksum
|
||||||
|
|
||||||
|
def getDateCreated(self):
|
||||||
|
item = self.item
|
||||||
|
try:
|
||||||
|
dateadded = item['addedAt']
|
||||||
|
dateadded = self.convert_date(dateadded)
|
||||||
|
except KeyError:
|
||||||
|
dateadded = None
|
||||||
|
return dateadded
|
||||||
|
|
||||||
|
def getUserData(self):
|
||||||
|
item = self.item
|
||||||
|
# Default
|
||||||
|
favorite = False
|
||||||
|
playcount = None
|
||||||
|
played = False
|
||||||
|
lastPlayedDate = None
|
||||||
|
resume = 0
|
||||||
|
rating = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
playcount = int(item['viewCount'])
|
||||||
|
except KeyError:
|
||||||
|
playcount = None
|
||||||
|
|
||||||
|
if playcount:
|
||||||
|
played = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
lastPlayedDate = int(item['lastViewedAt'])
|
||||||
|
lastPlayedDate = self.convert_date(lastPlayedDate)
|
||||||
|
except KeyError:
|
||||||
|
lastPlayedDate = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
resume = int(item['viewOffset'])
|
||||||
|
except KeyError:
|
||||||
|
resume = 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
'Favorite': favorite,
|
||||||
|
'PlayCount': playcount,
|
||||||
|
'Played': played,
|
||||||
|
'LastPlayedDate': lastPlayedDate,
|
||||||
|
'Resume': resume,
|
||||||
|
'Rating': rating
|
||||||
|
}
|
||||||
|
|
||||||
|
def getPeople(self):
|
||||||
|
"""
|
||||||
|
returns a dictionary of lists of people found in item.
|
||||||
|
|
||||||
|
{
|
||||||
|
'Director': list,
|
||||||
|
'Writer': list,
|
||||||
|
'Cast': list,
|
||||||
|
'Producer': list
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
item = self.item
|
||||||
|
item = item['_children']
|
||||||
|
# Process People
|
||||||
|
director = []
|
||||||
|
writer = []
|
||||||
|
cast = []
|
||||||
|
producer = []
|
||||||
|
for entry in item:
|
||||||
|
if entry['_elementType'] == 'Director':
|
||||||
|
director.append(entry['tag'])
|
||||||
|
elif entry['_elementType'] == 'Writer':
|
||||||
|
writer.append(entry['tag'])
|
||||||
|
elif entry['_elementType'] == 'Role':
|
||||||
|
cast.append(entry['tag'])
|
||||||
|
elif entry['_elementType'] == 'Producer':
|
||||||
|
producer.append(entry['tag'])
|
||||||
|
return {
|
||||||
|
'Director': director,
|
||||||
|
'Writer': writer,
|
||||||
|
'Cast': cast,
|
||||||
|
'Producer': producer
|
||||||
|
}
|
||||||
|
|
||||||
|
def getPeopleList(self):
|
||||||
|
"""
|
||||||
|
Returns a list of people from item, with a list item of the form
|
||||||
|
{
|
||||||
|
'Name': xxx,
|
||||||
|
'Type': xxx,
|
||||||
|
'Id': xxx
|
||||||
|
('Role': xxx for cast/actors only)
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
item = self.item['_children']
|
||||||
|
people = []
|
||||||
|
# Key of library: Plex-identifier. Value represents the Kodi/emby side
|
||||||
|
people_of_interest = {
|
||||||
|
'Director': 'Director',
|
||||||
|
'Writer': 'Writer',
|
||||||
|
'Role': 'Actor',
|
||||||
|
'Producer': 'Producer'
|
||||||
|
}
|
||||||
|
for entry in item:
|
||||||
|
if entry['_elementType'] in people_of_interest.keys():
|
||||||
|
name = entry['tag']
|
||||||
|
name_id = entry['id']
|
||||||
|
Type = entry['_elementType']
|
||||||
|
Type = people_of_interest[Type]
|
||||||
|
if Type == 'Actor':
|
||||||
|
Role = entry['role']
|
||||||
|
people.append({
|
||||||
|
'Name': name,
|
||||||
|
'Type': Type,
|
||||||
|
'Id': name_id,
|
||||||
|
'Role': Role
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
people.append({
|
||||||
|
'Name': name,
|
||||||
|
'Type': Type,
|
||||||
|
'Id': name_id
|
||||||
|
})
|
||||||
|
return people
|
||||||
|
|
||||||
|
def getGenres(self):
|
||||||
|
"""
|
||||||
|
returns a list of genres found in item. (Not a string!!)
|
||||||
|
"""
|
||||||
|
item = self.item
|
||||||
|
item = item['_children']
|
||||||
|
genre = []
|
||||||
|
for entry in item:
|
||||||
|
if entry['_elementType'] == 'Genre':
|
||||||
|
genre.append(entry['tag'])
|
||||||
|
return genre
|
||||||
|
|
||||||
|
def getProvider(self, providername):
|
||||||
|
"""
|
||||||
|
provider = getProvider(self, item, providername)
|
||||||
|
|
||||||
|
providername: imdb, tvdb, musicBrainzArtist, musicBrainzAlbum,
|
||||||
|
musicBrainzTrackId
|
||||||
|
|
||||||
|
Return IMDB: "tt1234567"
|
||||||
|
"""
|
||||||
|
item = self.item
|
||||||
|
imdb_regex = re.compile(r'''(
|
||||||
|
imdb:// # imdb tag, which will be followed be tt1234567
|
||||||
|
(tt\d{7}) # actual IMDB ID, e.g. tt1234567
|
||||||
|
\?? # zero or one ?
|
||||||
|
(.*) # rest, e.g. language setting
|
||||||
|
)''', re.VERBOSE)
|
||||||
|
try:
|
||||||
|
if "Imdb" in providername:
|
||||||
|
provider = imdb_regex.findall(item['guid'])
|
||||||
|
provider = provider[0][1]
|
||||||
|
elif "tvdb" in providername:
|
||||||
|
provider = item['ProviderIds']['Tvdb']
|
||||||
|
elif "musicBrainzArtist" in providername:
|
||||||
|
provider = item['ProviderIds']['MusicBrainzArtist']
|
||||||
|
elif "musicBrainzAlbum" in providername:
|
||||||
|
provider = item['ProviderIds']['MusicBrainzAlbum']
|
||||||
|
elif "musicBrainzTrackId" in providername:
|
||||||
|
provider = item['ProviderIds']['MusicBrainzTrackId']
|
||||||
|
except:
|
||||||
|
provider = None
|
||||||
|
return provider
|
||||||
|
|
||||||
|
def GetTitle(self):
|
||||||
|
item = self.item
|
||||||
|
title = item['title']
|
||||||
|
try:
|
||||||
|
sorttitle = item['titleSort']
|
||||||
|
except KeyError:
|
||||||
|
sorttitle = title
|
||||||
|
return title, sorttitle
|
||||||
|
|
||||||
|
def getRuntime(self):
|
||||||
|
"""
|
||||||
|
Resume point of time and runtime/totaltime. Rounded to 6th decimal.
|
||||||
|
|
||||||
|
Assumption: time for both resume and runtime is measured in
|
||||||
|
milliseconds on the Plex side and in seconds on the Kodi side.
|
||||||
|
"""
|
||||||
|
item = self.item
|
||||||
|
time_factor = 1/1000
|
||||||
|
runtime = item['duration'] * time_factor
|
||||||
|
try:
|
||||||
|
resume = item['viewOffset'] * time_factor
|
||||||
|
except KeyError:
|
||||||
|
resume = 0
|
||||||
|
resume = round(float(resume), 6)
|
||||||
|
runtime = round(float(runtime), 6)
|
||||||
|
return resume, runtime
|
||||||
|
|
||||||
|
def getMpaa(self):
|
||||||
|
# Convert more complex cases
|
||||||
|
item = self.item
|
||||||
|
try:
|
||||||
|
mpaa = item['contentRating']
|
||||||
|
except KeyError:
|
||||||
|
mpaa = None
|
||||||
|
if mpaa in ("NR", "UR"):
|
||||||
|
# Kodi seems to not like NR, but will accept Rated Not Rated
|
||||||
|
mpaa = "Rated Not Rated"
|
||||||
|
return mpaa
|
||||||
|
|
||||||
|
def getCountry(self):
|
||||||
|
"""
|
||||||
|
Returns a list of all countries found in item.
|
||||||
|
"""
|
||||||
|
item = self.item
|
||||||
|
item = item['_children']
|
||||||
|
country = []
|
||||||
|
for entry in item:
|
||||||
|
if entry['_elementType'] == 'Country':
|
||||||
|
country.append(entry['tag'])
|
||||||
|
return country
|
||||||
|
|
||||||
|
def getStudios(self):
|
||||||
|
item = self.item
|
||||||
|
studio = []
|
||||||
|
try:
|
||||||
|
studio.append(self.getStudio(item['studio']))
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return studio
|
||||||
|
|
||||||
|
def getStudio(self, studioName):
|
||||||
|
# Convert studio for Kodi to properly detect them
|
||||||
|
studios = {
|
||||||
|
'abc (us)': "ABC",
|
||||||
|
'fox (us)': "FOX",
|
||||||
|
'mtv (us)': "MTV",
|
||||||
|
'showcase (ca)': "Showcase",
|
||||||
|
'wgn america': "WGN"
|
||||||
|
}
|
||||||
|
return studios.get(studioName.lower(), studioName)
|
||||||
|
|
||||||
|
def joinList(self, listobject):
|
||||||
|
"""
|
||||||
|
Smart-joins the list into a single string using a " / " separator.
|
||||||
|
If the list is empty, smart_join returns an empty string.
|
||||||
|
"""
|
||||||
|
string = " / ".join(listobject)
|
||||||
|
return string
|
||||||
|
|
||||||
|
def getFilePath(self):
|
||||||
|
item = self.item
|
||||||
|
try:
|
||||||
|
filepath = item['key']
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
filepath = ""
|
||||||
|
|
||||||
|
else:
|
||||||
|
if "\\\\" in filepath:
|
||||||
|
# append smb protocol
|
||||||
|
filepath = filepath.replace("\\\\", "smb://")
|
||||||
|
filepath = filepath.replace("\\", "/")
|
||||||
|
|
||||||
|
if item.get('VideoType'):
|
||||||
|
videotype = item['VideoType']
|
||||||
|
# Specific format modification
|
||||||
|
if 'Dvd'in videotype:
|
||||||
|
filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath
|
||||||
|
elif 'Bluray' in videotype:
|
||||||
|
filepath = "%s/BDMV/index.bdmv" % filepath
|
||||||
|
|
||||||
|
if "\\" in filepath:
|
||||||
|
# Local path scenario, with special videotype
|
||||||
|
filepath = filepath.replace("/", "\\")
|
||||||
|
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
def getMediaStreams(self):
|
||||||
|
item = self.item
|
||||||
|
item = item['_children']
|
||||||
|
videotracks = []
|
||||||
|
audiotracks = []
|
||||||
|
subtitlelanguages = []
|
||||||
|
|
||||||
|
MediaStreams = []
|
||||||
|
aspectratio = None
|
||||||
|
for entry in item:
|
||||||
|
if entry['_elementType'] == 'Media':
|
||||||
|
MediaStreams.append(entry)
|
||||||
|
try:
|
||||||
|
aspectratio = entry['aspectRatio']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
# Abort if no Media found
|
||||||
|
if not MediaStreams:
|
||||||
|
return
|
||||||
|
# Loop over parts:
|
||||||
|
# TODO: what if several Media tags exist?!?
|
||||||
|
for part in MediaStreams[0]['_children']:
|
||||||
|
container = part['container'].lower()
|
||||||
|
for mediaStream in part['_children']:
|
||||||
|
try:
|
||||||
|
type = mediaStream['streamType']
|
||||||
|
except KeyError:
|
||||||
|
type = None
|
||||||
|
if type == 1: # Video streams
|
||||||
|
videotrack = {}
|
||||||
|
videotrack['videocodec'] = mediaStream['codec'].lower()
|
||||||
|
if "msmpeg4" in videotrack['videocodec']:
|
||||||
|
videotrack['videocodec'] = "divx"
|
||||||
|
elif "mpeg4" in videotrack['videocodec']:
|
||||||
|
# if "simple profile" in profile or profile == "":
|
||||||
|
# videotrack['videocodec'] = "xvid"
|
||||||
|
pass
|
||||||
|
elif "h264" in videotrack['videocodec']:
|
||||||
|
if container in ("mp4", "mov", "m4v"):
|
||||||
|
videotrack['videocodec'] = "avc1"
|
||||||
|
videotrack['height'] = mediaStream.get('height')
|
||||||
|
videotrack['width'] = mediaStream.get('width')
|
||||||
|
# TODO: 3d Movies?!?
|
||||||
|
# videotrack['Video3DFormat'] = item.get('Video3DFormat')
|
||||||
|
try:
|
||||||
|
aspectratio = mediaStream['aspectRatio']
|
||||||
|
except KeyError:
|
||||||
|
if not aspectratio:
|
||||||
|
aspectratio = round(float(videotrack['width'] / videotrack['height']), 6)
|
||||||
|
videotrack['aspectratio'] = aspectratio
|
||||||
|
# TODO: Video 3d format
|
||||||
|
videotrack['video3DFormat'] = None
|
||||||
|
videotracks.append(videotrack)
|
||||||
|
|
||||||
|
elif type == 2: # Audio streams
|
||||||
|
audiotrack = {}
|
||||||
|
audiotrack['audiocodec'] = mediaStream['codec'].lower()
|
||||||
|
profile = mediaStream['codecID'].lower()
|
||||||
|
if "dca" in audiotrack['audiocodec'] and "dts-hd ma" in profile:
|
||||||
|
audiotrack['audiocodec'] = "dtshd_ma"
|
||||||
|
audiotrack['channels'] = mediaStream.get('channels')
|
||||||
|
try:
|
||||||
|
audiotrack['audiolanguage'] = mediaStream.get('language')
|
||||||
|
except KeyError:
|
||||||
|
audiotrack['audiolanguage'] = 'unknown'
|
||||||
|
audiotracks.append(audiotrack)
|
||||||
|
|
||||||
|
elif type == 3: # Subtitle streams
|
||||||
|
try:
|
||||||
|
subtitlelanguages.append(mediaStream['language'])
|
||||||
|
except:
|
||||||
|
subtitlelanguages.append("Unknown")
|
||||||
|
return {
|
||||||
|
'video': videotracks,
|
||||||
|
'audio': audiotracks,
|
||||||
|
'subtitle': subtitlelanguages
|
||||||
|
}
|
||||||
|
|
|
@ -427,9 +427,7 @@ class Artwork():
|
||||||
|
|
||||||
server = self.server
|
server = self.server
|
||||||
|
|
||||||
id = item['Id']
|
id = item['key']
|
||||||
artworks = item['ImageTags']
|
|
||||||
backdrops = item['BackdropImageTags']
|
|
||||||
|
|
||||||
maxHeight = 10000
|
maxHeight = 10000
|
||||||
maxWidth = 10000
|
maxWidth = 10000
|
||||||
|
@ -453,26 +451,20 @@ class Artwork():
|
||||||
}
|
}
|
||||||
|
|
||||||
# Process backdrops
|
# Process backdrops
|
||||||
backdropIndex = 0
|
# Get background artwork URL
|
||||||
for backdroptag in backdrops:
|
try:
|
||||||
artwork = (
|
background = item['art']
|
||||||
"%s/emby/Items/%s/Images/Backdrop/%s?"
|
background = "%s%s" % (server, background)
|
||||||
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
except KeyError:
|
||||||
% (server, id, backdropIndex,
|
background = ""
|
||||||
maxWidth, maxHeight, backdroptag, customquery))
|
allartworks['Backdrop'].append(background)
|
||||||
allartworks['Backdrop'].append(artwork)
|
# Get primary "thumb" pictures:
|
||||||
backdropIndex += 1
|
try:
|
||||||
|
primary = item['thumb']
|
||||||
# Process the rest of the artwork
|
primary = "%s%s" % (server, primary)
|
||||||
for art in artworks:
|
except KeyError:
|
||||||
# Filter backcover
|
primary = ""
|
||||||
if art != "BoxRear":
|
allartworks['Primary'] = primary
|
||||||
tag = artworks[art]
|
|
||||||
artwork = (
|
|
||||||
"%s/emby/Items/%s/Images/%s/0?"
|
|
||||||
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
|
||||||
% (server, id, art, maxWidth, maxHeight, tag, customquery))
|
|
||||||
allartworks[art] = artwork
|
|
||||||
|
|
||||||
# Process parent items if the main item is missing artwork
|
# Process parent items if the main item is missing artwork
|
||||||
if parentInfo:
|
if parentInfo:
|
||||||
|
|
|
@ -19,6 +19,8 @@ import embydb_functions as embydb
|
||||||
import kodidb_functions as kodidb
|
import kodidb_functions as kodidb
|
||||||
import read_embyserver as embyserver
|
import read_embyserver as embyserver
|
||||||
|
|
||||||
|
import PlexAPI
|
||||||
|
|
||||||
##################################################################################################
|
##################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
@ -270,12 +272,12 @@ class Movies(Items):
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
kodi_db = self.kodi_db
|
kodi_db = self.kodi_db
|
||||||
artwork = self.artwork
|
artwork = self.artwork
|
||||||
API = api.API(item)
|
API = PlexAPI.API(item)
|
||||||
|
|
||||||
# If the item already exist in the local Kodi DB we'll perform a full item update
|
# If the item already exist in the local Kodi DB we'll perform a full item update
|
||||||
# If the item doesn't exist, we'll add it to the database
|
# If the item doesn't exist, we'll add it to the database
|
||||||
update_item = True
|
update_item = True
|
||||||
itemid = item['Id']
|
itemid = item['key']
|
||||||
emby_dbitem = emby_db.getItem_byId(itemid)
|
emby_dbitem = emby_db.getItem_byId(itemid)
|
||||||
try:
|
try:
|
||||||
movieid = emby_dbitem[0]
|
movieid = emby_dbitem[0]
|
||||||
|
@ -290,11 +292,6 @@ class Movies(Items):
|
||||||
kodicursor.execute("select coalesce(max(idMovie),0) from movie")
|
kodicursor.execute("select coalesce(max(idMovie),0) from movie")
|
||||||
movieid = kodicursor.fetchone()[0] + 1
|
movieid = kodicursor.fetchone()[0] + 1
|
||||||
|
|
||||||
if not viewtag or not viewid:
|
|
||||||
# Get view tag from emby
|
|
||||||
viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
|
|
||||||
self.logMsg("View tag found: %s" % viewtag, 2)
|
|
||||||
|
|
||||||
# fileId information
|
# fileId information
|
||||||
checksum = API.getChecksum()
|
checksum = API.getChecksum()
|
||||||
dateadded = API.getDateCreated()
|
dateadded = API.getDateCreated()
|
||||||
|
@ -304,51 +301,30 @@ class Movies(Items):
|
||||||
|
|
||||||
# item details
|
# item details
|
||||||
people = API.getPeople()
|
people = API.getPeople()
|
||||||
writer = " / ".join(people['Writer'])
|
writer = API.joinList(people['Writer'])
|
||||||
director = " / ".join(people['Director'])
|
director = API.joinList(people['Director'])
|
||||||
genres = item['Genres']
|
genres = API.getGenres()
|
||||||
title = item['Name']
|
title, sorttitle = API.GetTitle()
|
||||||
plot = API.getOverview()
|
plot = item['summary']
|
||||||
shortplot = item.get('ShortOverview')
|
shortplot = None
|
||||||
tagline = API.getTagline()
|
tagline = item.get('tagline', '')
|
||||||
votecount = item.get('VoteCount')
|
votecount = 0
|
||||||
rating = item.get('CommunityRating')
|
rating = item.get('audienceRating', None)
|
||||||
year = item.get('ProductionYear')
|
year = item.get('year', None)
|
||||||
imdb = API.getProvider('Imdb')
|
imdb = API.getProvider('Imdb')
|
||||||
sorttitle = item['SortName']
|
resume, runtime = API.getRuntime()
|
||||||
runtime = API.getRuntime()
|
|
||||||
mpaa = API.getMpaa()
|
mpaa = API.getMpaa()
|
||||||
genre = " / ".join(genres)
|
genre = API.joinList(genres)
|
||||||
country = API.getCountry()
|
countries = API.getCountry()
|
||||||
|
country = API.joinList(countries)
|
||||||
studios = API.getStudios()
|
studios = API.getStudios()
|
||||||
try:
|
try:
|
||||||
studio = studios[0]
|
studio = studios[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
studio = None
|
studio = None
|
||||||
|
|
||||||
if item.get('LocalTrailerCount'):
|
# TODO: trailers
|
||||||
# There's a local trailer
|
trailer = None
|
||||||
url = (
|
|
||||||
"{server}/emby/Users/{UserId}/Items/%s/LocalTrailers?format=json"
|
|
||||||
% itemid
|
|
||||||
)
|
|
||||||
result = self.doUtils.downloadUrl(url)
|
|
||||||
trailer = "plugin://plugin.video.plexkodiconnect/trailer/?id=%s&mode=play" % result[0]['Id']
|
|
||||||
else:
|
|
||||||
# Try to get the youtube trailer
|
|
||||||
try:
|
|
||||||
trailer = item['RemoteTrailers'][0]['Url']
|
|
||||||
except (KeyError, IndexError):
|
|
||||||
trailer = None
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
trailerId = trailer.rsplit('=', 1)[1]
|
|
||||||
except IndexError:
|
|
||||||
self.logMsg("Failed to process trailer: %s" % trailer)
|
|
||||||
trailer = None
|
|
||||||
else:
|
|
||||||
trailer = "plugin://plugin.video.youtube/play/?video_id=%s" % trailerId
|
|
||||||
|
|
||||||
|
|
||||||
##### GET THE FILE AND PATH #####
|
##### GET THE FILE AND PATH #####
|
||||||
playurl = API.getFilePath()
|
playurl = API.getFilePath()
|
||||||
|
@ -455,9 +431,11 @@ class Movies(Items):
|
||||||
kodicursor.execute(query, (pathid, filename, dateadded, fileid))
|
kodicursor.execute(query, (pathid, filename, dateadded, fileid))
|
||||||
|
|
||||||
# Process countries
|
# Process countries
|
||||||
kodi_db.addCountries(movieid, item['ProductionLocations'], "movie")
|
kodi_db.addCountries(movieid, countries, "movie")
|
||||||
# Process cast
|
# Process cast
|
||||||
people = artwork.getPeopleArtwork(item['People'])
|
people = API.getPeopleList()
|
||||||
|
# TODO: get IMDB pictures?
|
||||||
|
people = artwork.getPeopleArtwork(people)
|
||||||
kodi_db.addPeople(movieid, people, "movie")
|
kodi_db.addPeople(movieid, people, "movie")
|
||||||
# Process genres
|
# Process genres
|
||||||
kodi_db.addGenres(movieid, genres, "movie")
|
kodi_db.addGenres(movieid, genres, "movie")
|
||||||
|
@ -469,13 +447,13 @@ class Movies(Items):
|
||||||
# Process studios
|
# Process studios
|
||||||
kodi_db.addStudios(movieid, studios, "movie")
|
kodi_db.addStudios(movieid, studios, "movie")
|
||||||
# Process tags: view, emby tags
|
# Process tags: view, emby tags
|
||||||
tags = [viewtag]
|
# tags = [viewtag]
|
||||||
tags.extend(item['Tags'])
|
# tags.extend(item['Tags'])
|
||||||
if userdata['Favorite']:
|
# if userdata['Favorite']:
|
||||||
tags.append("Favorite movies")
|
# tags.append("Favorite movies")
|
||||||
kodi_db.addTags(movieid, tags, "movie")
|
# kodi_db.addTags(movieid, tags, "movie")
|
||||||
# Process playstates
|
# Process playstates
|
||||||
resume = API.adjustResume(userdata['Resume'])
|
# resume = API.adjustResume(userdata['Resume'])
|
||||||
total = round(float(runtime), 6)
|
total = round(float(runtime), 6)
|
||||||
kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
|
kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ import read_embyserver as embyserver
|
||||||
import userclient
|
import userclient
|
||||||
import videonodes
|
import videonodes
|
||||||
|
|
||||||
|
import PlexAPI
|
||||||
|
|
||||||
##################################################################################################
|
##################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,6 +53,7 @@ class LibrarySync(threading.Thread):
|
||||||
self.user = userclient.UserClient()
|
self.user = userclient.UserClient()
|
||||||
self.emby = embyserver.Read_EmbyServer()
|
self.emby = embyserver.Read_EmbyServer()
|
||||||
self.vnodes = videonodes.VideoNodes()
|
self.vnodes = videonodes.VideoNodes()
|
||||||
|
self.plx = PlexAPI.PlexAPI()
|
||||||
|
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
@ -76,7 +79,7 @@ class LibrarySync(threading.Thread):
|
||||||
if utils.settings('SyncInstallRunDone') == "true":
|
if utils.settings('SyncInstallRunDone') == "true":
|
||||||
|
|
||||||
# Validate views
|
# Validate views
|
||||||
self.refreshViews()
|
# self.refreshViews()
|
||||||
completed = False
|
completed = False
|
||||||
# Verify if server plugin is installed.
|
# Verify if server plugin is installed.
|
||||||
if utils.settings('serverSync') == "true":
|
if utils.settings('serverSync') == "true":
|
||||||
|
@ -235,7 +238,7 @@ class LibrarySync(threading.Thread):
|
||||||
}
|
}
|
||||||
|
|
||||||
process = {
|
process = {
|
||||||
'movies': self.movies,
|
'movies': self.PlexMovies,
|
||||||
}
|
}
|
||||||
for itemtype in process:
|
for itemtype in process:
|
||||||
startTime = datetime.now()
|
startTime = datetime.now()
|
||||||
|
@ -461,6 +464,118 @@ class LibrarySync(threading.Thread):
|
||||||
utils.window('Emby.nodes.total', str(totalnodes))
|
utils.window('Emby.nodes.total', str(totalnodes))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def PlexMovies(self, embycursor, kodicursor, pdialog, compare=False):
|
||||||
|
# Get movies from emby
|
||||||
|
emby = self.emby
|
||||||
|
emby_db = embydb.Embydb_Functions(embycursor)
|
||||||
|
movies = itemtypes.Movies(embycursor, kodicursor)
|
||||||
|
|
||||||
|
views = self.plx.GetPlexCollections('movies')
|
||||||
|
self.logMsg("Media folders: %s" % views, 1)
|
||||||
|
|
||||||
|
if compare:
|
||||||
|
# Pull the list of movies and boxsets in Kodi
|
||||||
|
try:
|
||||||
|
all_kodimovies = dict(emby_db.getChecksum('Movie'))
|
||||||
|
except ValueError:
|
||||||
|
all_kodimovies = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
all_kodisets = dict(emby_db.getChecksum('BoxSet'))
|
||||||
|
except ValueError:
|
||||||
|
all_kodisets = {}
|
||||||
|
|
||||||
|
all_embymoviesIds = set()
|
||||||
|
all_embyboxsetsIds = set()
|
||||||
|
updatelist = []
|
||||||
|
|
||||||
|
##### PROCESS MOVIES #####
|
||||||
|
for view in views:
|
||||||
|
|
||||||
|
if self.shouldStop():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get items per view
|
||||||
|
viewId = view['id']
|
||||||
|
viewName = view['name']
|
||||||
|
|
||||||
|
if pdialog:
|
||||||
|
pdialog.update(
|
||||||
|
heading=self.addonName,
|
||||||
|
message="Gathering movies from view: %s..." % viewName)
|
||||||
|
|
||||||
|
if compare:
|
||||||
|
# Manual sync
|
||||||
|
if pdialog:
|
||||||
|
pdialog.update(
|
||||||
|
heading=self.addonName,
|
||||||
|
message="Comparing movies from view: %s..." % viewName)
|
||||||
|
|
||||||
|
all_embymovies = self.plx.GetPlexSectionResults(viewId)
|
||||||
|
embymovies = []
|
||||||
|
for embymovie in all_embymovies:
|
||||||
|
|
||||||
|
if self.shouldStop():
|
||||||
|
return False
|
||||||
|
|
||||||
|
API = PlexAPI.API(embymovie)
|
||||||
|
itemid = embymovie['key']
|
||||||
|
all_embymoviesIds.add(itemid)
|
||||||
|
|
||||||
|
if all_kodimovies.get(itemid) != API.getChecksum():
|
||||||
|
# Only update if movie is not in Kodi or checksum is different
|
||||||
|
updatelist.append(itemid)
|
||||||
|
embymovies.append(embymovie)
|
||||||
|
|
||||||
|
self.logMsg("Movies to update for %s: %s" % (viewName, updatelist), 1)
|
||||||
|
total = len(updatelist)
|
||||||
|
del updatelist[:]
|
||||||
|
else:
|
||||||
|
# Initial or repair sync
|
||||||
|
embymovies = self.plx.GetPlexMovies(viewId)
|
||||||
|
total = len(embymovies)
|
||||||
|
|
||||||
|
if pdialog:
|
||||||
|
pdialog.update(heading="Processing %s / %s items" % (viewName, total))
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
for embymovie in embymovies:
|
||||||
|
# Process individual movies
|
||||||
|
if self.shouldStop():
|
||||||
|
return False
|
||||||
|
|
||||||
|
title = embymovie['title']
|
||||||
|
if pdialog:
|
||||||
|
percentage = int((float(count) / float(total))*100)
|
||||||
|
pdialog.update(percentage, message=title)
|
||||||
|
count += 1
|
||||||
|
detailed = self.plx.GetPlexMetadata(embymovie['key'])
|
||||||
|
movies.add_update(detailed, viewName, viewId)
|
||||||
|
else:
|
||||||
|
self.logMsg("Movies finished.", 2)
|
||||||
|
|
||||||
|
##### PROCESS DELETES #####
|
||||||
|
if compare:
|
||||||
|
# Manual sync, process deletes
|
||||||
|
for kodimovie in all_kodimovies:
|
||||||
|
if kodimovie not in all_embymoviesIds:
|
||||||
|
movies.remove(kodimovie)
|
||||||
|
else:
|
||||||
|
self.logMsg("Movies compare finished.", 1)
|
||||||
|
|
||||||
|
for boxset in all_kodisets:
|
||||||
|
if boxset not in all_embyboxsetsIds:
|
||||||
|
movies.remove(boxset)
|
||||||
|
else:
|
||||||
|
self.logMsg("Boxsets compare finished.", 1)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def movies(self, embycursor, kodicursor, pdialog, compare=False):
|
def movies(self, embycursor, kodicursor, pdialog, compare=False):
|
||||||
# Get movies from emby
|
# Get movies from emby
|
||||||
emby = self.emby
|
emby = self.emby
|
||||||
|
|
Loading…
Reference in a new issue