Merge branch 'develop' into translations

This commit is contained in:
tomkat83 2017-04-07 11:41:24 +02:00
commit f8fe6b6659
10 changed files with 693 additions and 563 deletions

View file

@ -39,6 +39,7 @@ import xml.etree.ElementTree as etree
from re import compile as re_compile, sub from re import compile as re_compile, sub
from json import dumps from json import dumps
from urllib import urlencode, quote_plus, unquote from urllib import urlencode, quote_plus, unquote
from os import path as os_path
import xbmcgui import xbmcgui
from xbmc import sleep, executebuiltin from xbmc import sleep, executebuiltin
@ -2186,13 +2187,38 @@ class API():
# Several streams/files available. # Several streams/files available.
dialoglist = [] dialoglist = []
for entry in self.item.findall('./Media'): for entry in self.item.findall('./Media'):
dialoglist.append( # Get additional info (filename / languages)
"%sp %s - %s (%s)" filename = None
% (entry.attrib.get('videoResolution', 'unknown'), if 'file' in entry[0].attrib:
entry.attrib.get('videoCodec', 'unknown'), filename = os_path.basename(entry[0].attrib['file'])
entry.attrib.get('audioProfile', 'unknown'), # Languages of audio streams
entry.attrib.get('audioCodec', 'unknown')) languages = []
) for stream in entry[0]:
if (stream.attrib['streamType'] == '1' and
'language' in stream.attrib):
languages.append(stream.attrib['language'])
languages = ', '.join(languages)
if filename:
option = tryEncode(filename)
if languages:
if option:
option = '%s (%s): ' % (option, tryEncode(languages))
else:
option = '%s: ' % tryEncode(languages)
if 'videoResolution' in entry.attrib:
option = '%s%sp ' % (option,
entry.attrib.get('videoResolution'))
if 'videoCodec' in entry.attrib:
option = '%s%s' % (option,
entry.attrib.get('videoCodec'))
option = option.strip() + ' - '
if 'audioProfile' in entry.attrib:
option = '%s%s ' % (option,
entry.attrib.get('audioProfile'))
if 'audioCodec' in entry.attrib:
option = '%s%s ' % (option,
entry.attrib.get('audioCodec'))
dialoglist.append(option)
media = xbmcgui.Dialog().select('Select stream', dialoglist) media = xbmcgui.Dialog().select('Select stream', dialoglist)
else: else:
media = 0 media = 0

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging from logging import getLogger
from urllib import urlencode from urllib import urlencode
from ast import literal_eval from ast import literal_eval
from urlparse import urlparse, parse_qsl from urlparse import urlparse, parse_qsl
@ -12,7 +12,9 @@ from variables import PLEX_TO_KODI_TIMEFACTOR
############################################################################### ###############################################################################
log = logging.getLogger("PLEX."+__name__) log = getLogger("PLEX."+__name__)
CONTAINERSIZE = int(settings('limitindex'))
############################################################################### ###############################################################################
@ -141,7 +143,7 @@ def GetPlexMetadata(key):
return xml return xml
def GetAllPlexChildren(key, containerSize=None): def GetAllPlexChildren(key):
""" """
Returns a list (raw xml API dump) of all Plex children for the key. Returns a list (raw xml API dump) of all Plex children for the key.
(e.g. /library/metadata/194853/children pointing to a season) (e.g. /library/metadata/194853/children pointing to a season)
@ -149,11 +151,10 @@ def GetAllPlexChildren(key, containerSize=None):
Input: Input:
key Key to a Plex item, e.g. 12345 key Key to a Plex item, e.g. 12345
""" """
url = "{server}/library/metadata/%s/children?" % key return DownloadChunks("{server}/library/metadata/%s/children?" % key)
return DownloadChunks(url, containerSize)
def GetPlexSectionResults(viewId, args=None, containerSize=None): def GetPlexSectionResults(viewId, args=None):
""" """
Returns a list (XML API dump) of all Plex items in the Plex Returns a list (XML API dump) of all Plex items in the Plex
section with key = viewId. section with key = viewId.
@ -166,38 +167,23 @@ def GetPlexSectionResults(viewId, args=None, containerSize=None):
url = "{server}/library/sections/%s/all?" % viewId url = "{server}/library/sections/%s/all?" % viewId
if args: if args:
url += urlencode(args) + '&' url += urlencode(args) + '&'
return DownloadChunks(url, containerSize) return DownloadChunks(url)
def DownloadChunks(url, containerSize): def DownloadChunks(url):
""" """
Downloads PMS url in chunks of containerSize (int). Downloads PMS url in chunks of CONTAINERSIZE.
If containerSize is None: ONE xml is fetched directly
url MUST end with '?' (if no other url encoded args are present) or '&' url MUST end with '?' (if no other url encoded args are present) or '&'
Returns a stitched-together xml or None. Returns a stitched-together xml or None.
""" """
if containerSize is None:
# Get rid of '?' or '&' at the end of url
xml = downloadutils.DownloadUtils().downloadUrl(url[:-1])
if xml == 401:
return 401
try:
xml.attrib
except AttributeError:
# Nope, not an XML, abort
log.error("Error getting url %s" % url[:-1])
return None
else:
return xml
xml = None xml = None
pos = 0 pos = 0
errorCounter = 0 errorCounter = 0
while errorCounter < 10: while errorCounter < 10:
args = { args = {
'X-Plex-Container-Size': containerSize, 'X-Plex-Container-Size': CONTAINERSIZE,
'X-Plex-Container-Start': pos 'X-Plex-Container-Start': pos
} }
xmlpart = downloadutils.DownloadUtils().downloadUrl( xmlpart = downloadutils.DownloadUtils().downloadUrl(
@ -208,33 +194,32 @@ def DownloadChunks(url, containerSize):
except AttributeError: except AttributeError:
log.error('Error while downloading chunks: %s' log.error('Error while downloading chunks: %s'
% (url + urlencode(args))) % (url + urlencode(args)))
pos += containerSize pos += CONTAINERSIZE
errorCounter += 1 errorCounter += 1
continue continue
# Very first run: starting xml (to retain data in xml's root!) # Very first run: starting xml (to retain data in xml's root!)
if xml is None: if xml is None:
xml = deepcopy(xmlpart) xml = deepcopy(xmlpart)
if len(xmlpart) < containerSize: if len(xmlpart) < CONTAINERSIZE:
break break
else: else:
pos += containerSize pos += CONTAINERSIZE
continue continue
# Build answer xml - containing the entire library # Build answer xml - containing the entire library
for child in xmlpart: for child in xmlpart:
xml.append(child) xml.append(child)
# Done as soon as we don't receive a full complement of items # Done as soon as we don't receive a full complement of items
if len(xmlpart) < containerSize: if len(xmlpart) < CONTAINERSIZE:
break break
pos += containerSize pos += CONTAINERSIZE
if errorCounter == 10: if errorCounter == 10:
log.error('Fatal error while downloading chunks for %s' % url) log.error('Fatal error while downloading chunks for %s' % url)
return None return None
return xml return xml
def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None, def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None):
containerSize=None):
""" """
Returns a list (raw XML API dump) of all Plex subitems for the key. Returns a list (raw XML API dump) of all Plex subitems for the key.
(e.g. /library/sections/2/allLeaves pointing to all TV shows) (e.g. /library/sections/2/allLeaves pointing to all TV shows)
@ -245,7 +230,6 @@ def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None,
since that point of time until now. since that point of time until now.
updatedAt Unix timestamp; only retrieves PMS items updated updatedAt Unix timestamp; only retrieves PMS items updated
by the PMS since that point of time until now. by the PMS since that point of time until now.
containerSize Number of items simultaneously fetched from PMS
If lastViewedAt and updatedAt=None, ALL PMS items are returned. If lastViewedAt and updatedAt=None, ALL PMS items are returned.
@ -265,14 +249,13 @@ def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None,
url += '?' + '&'.join(args) + '&' url += '?' + '&'.join(args) + '&'
else: else:
url += '?' url += '?'
return DownloadChunks(url, containerSize) return DownloadChunks(url)
def GetPlexOnDeck(viewId, containerSize=None): def GetPlexOnDeck(viewId):
""" """
""" """
url = "{server}/library/sections/%s/onDeck?" % viewId return DownloadChunks("{server}/library/sections/%s/onDeck?" % viewId)
return DownloadChunks(url, containerSize)
def GetPlexCollections(mediatype): def GetPlexCollections(mediatype):

View file

@ -791,9 +791,7 @@ def browse_plex(key=None, plex_section_id=None):
if key: if key:
xml = downloadutils.DownloadUtils().downloadUrl('{server}%s' % key) xml = downloadutils.DownloadUtils().downloadUrl('{server}%s' % key)
else: else:
xml = GetPlexSectionResults( xml = GetPlexSectionResults(plex_section_id)
plex_section_id,
containerSize=int(settings('limitindex')))
try: try:
xml[0].attrib xml[0].attrib
except (ValueError, AttributeError, IndexError, TypeError): except (ValueError, AttributeError, IndexError, TypeError):

View file

@ -9,8 +9,7 @@ from datetime import datetime
from xbmc import sleep from xbmc import sleep
import artwork import artwork
from utils import tryEncode, tryDecode, settings, window, kodiSQL, \ from utils import tryEncode, tryDecode, window, kodiSQL, CatchExceptions
CatchExceptions
import plexdb_functions as plexdb import plexdb_functions as plexdb
import kodidb_functions as kodidb import kodidb_functions as kodidb
@ -1259,14 +1258,6 @@ class TVShows(Items):
class Music(Items): class Music(Items):
def __init__(self):
Items.__init__(self)
self.directstream = settings('streamMusic') == "true"
self.enableimportsongrating = settings('enableImportSongRating') == "true"
self.enableexportsongrating = settings('enableExportSongRating') == "true"
self.enableupdatesongrating = settings('enableUpdateSongRating') == "true"
def __enter__(self): def __enter__(self):
""" """
OVERWRITE this method, because we need to open another DB. OVERWRITE this method, because we need to open another DB.
@ -1305,7 +1296,7 @@ class Music(Items):
name, sortname = API.getTitle() name, sortname = API.getTitle()
# musicBrainzId = API.getProvider('MusicBrainzArtist') # musicBrainzId = API.getProvider('MusicBrainzArtist')
musicBrainzId = None musicBrainzId = None
genres = API.joinList(API.getGenres()) genres = ' / '.join(API.getGenres())
bio = API.getPlot() bio = API.getPlot()
# Associate artwork # Associate artwork
@ -1344,31 +1335,32 @@ class Music(Items):
# Process the artist # Process the artist
if v.KODIVERSION >= 16: if v.KODIVERSION >= 16:
query = ' '.join(( query = '''
UPDATE artist
"UPDATE artist", SET strGenres = ?, strBiography = ?, strImage = ?,
"SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,", strFanart = ?, lastScraped = ?
"lastScraped = ?", WHERE idArtist = ?
"WHERE idArtist = ?" '''
))
kodicursor.execute(query, (genres, bio, thumb, fanart, kodicursor.execute(query, (genres, bio, thumb, fanart,
lastScraped, artistid)) lastScraped, artistid))
else: else:
query = ' '.join(( query = '''
UPDATE artist
"UPDATE artist", SET strGenres = ?, strBiography = ?, strImage = ?,
"SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,", strFanart = ?, lastScraped = ?, dateAdded = ?
"lastScraped = ?, dateAdded = ?", WHERE idArtist = ?
"WHERE idArtist = ?" '''
))
kodicursor.execute(query, (genres, bio, thumb, fanart, lastScraped, kodicursor.execute(query, (genres, bio, thumb, fanart, lastScraped,
dateadded, artistid)) dateadded, artistid))
# Update artwork # Update artwork
artwork.addArtwork(artworks, artistid, "artist", kodicursor) artwork.addArtwork(artworks, artistid, v.KODI_TYPE_ARTIST, kodicursor)
@CatchExceptions(warnuser=True) @CatchExceptions(warnuser=True)
def add_updateAlbum(self, item, viewtag=None, viewid=None): def add_updateAlbum(self, item, viewtag=None, viewid=None, children=None):
"""
children: list of child xml's, so in this case songs
"""
kodicursor = self.kodicursor kodicursor = self.kodicursor
plex_db = self.plex_db plex_db = self.plex_db
artwork = self.artwork artwork = self.artwork
@ -1396,21 +1388,21 @@ class Music(Items):
# musicBrainzId = API.getProvider('MusicBrainzAlbum') # musicBrainzId = API.getProvider('MusicBrainzAlbum')
musicBrainzId = None musicBrainzId = None
year = API.getYear() year = API.getYear()
genres = API.getGenres() self.genres = API.getGenres()
genre = API.joinList(genres) self.genre = ' / '.join(self.genres)
bio = API.getPlot() bio = API.getPlot()
rating = userdata['UserRating'] rating = userdata['UserRating']
studio = API.getMusicStudio() studio = API.getMusicStudio()
# artists = item['AlbumArtists']
# if not artists:
# artists = item['ArtistItems']
# artistname = []
# for artist in artists:
# artistname.append(artist['Name'])
artistname = item.attrib.get('parentTitle') artistname = item.attrib.get('parentTitle')
if not artistname: if not artistname:
artistname = item.attrib.get('originalTitle') artistname = item.attrib.get('originalTitle')
# See if we have a compilation - Plex does NOT feature a compilation
# flag for albums
self.compilation = 0
for child in children:
if child.attrib.get('originalTitle') is not None:
self.compilation = 1
break
# Associate artwork # Associate artwork
artworks = API.getAllArtwork(parentInfo=True) artworks = API.getAllArtwork(parentInfo=True)
thumb = artworks['Primary'] thumb = artworks['Primary']
@ -1442,56 +1434,54 @@ class Music(Items):
# Process the album info # Process the album info
if v.KODIVERSION >= 17: if v.KODIVERSION >= 17:
# Kodi Krypton # Kodi Krypton
query = ' '.join(( query = '''
UPDATE album
"UPDATE album", SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?,
"SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", strImage = ?, iUserrating = ?, lastScraped = ?,
"iUserrating = ?, lastScraped = ?, strReleaseType = ?, " strReleaseType = ?, strLabel = ?, bCompilation = ?
"strLabel = ? ", WHERE idAlbum = ?
"WHERE idAlbum = ?" '''
)) kodicursor.execute(query, (artistname, year, self.genre, bio,
kodicursor.execute(query, (artistname, year, genre, bio, thumb, thumb, rating, lastScraped,
rating, lastScraped, "album", studio, v.KODI_TYPE_ALBUM, studio,
albumid)) self.compilation, albumid))
elif v.KODIVERSION == 16: elif v.KODIVERSION == 16:
# Kodi Jarvis # Kodi Jarvis
query = ' '.join(( query = '''
UPDATE album
"UPDATE album", SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?,
"SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", strImage = ?, iRating = ?, lastScraped = ?,
"iRating = ?, lastScraped = ?, strReleaseType = ?, " strReleaseType = ?, strLabel = ?, bCompilation = ?
"strLabel = ? ", WHERE idAlbum = ?
"WHERE idAlbum = ?" '''
)) kodicursor.execute(query, (artistname, year, self.genre, bio,
kodicursor.execute(query, (artistname, year, genre, bio, thumb, thumb, rating, lastScraped,
rating, lastScraped, "album", studio, v.KODI_TYPE_ALBUM, studio,
albumid)) self.compilation, albumid))
elif v.KODIVERSION == 15: elif v.KODIVERSION == 15:
# Kodi Isengard # Kodi Isengard
query = ' '.join(( query = '''
UPDATE album
"UPDATE album", SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?,
"SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", strImage = ?, iRating = ?, lastScraped = ?, dateAdded = ?,
"iRating = ?, lastScraped = ?, dateAdded = ?, " strReleaseType = ?, strLabel = ?
"strReleaseType = ?, strLabel = ? ", WHERE idAlbum = ?
"WHERE idAlbum = ?" '''
)) kodicursor.execute(query, (artistname, year, self.genre, bio,
kodicursor.execute(query, (artistname, year, genre, bio, thumb, thumb, rating, lastScraped, dateadded,
rating, lastScraped, dateadded, v.KODI_TYPE_ALBUM, studio, albumid))
"album", studio, albumid))
else: else:
# Kodi Helix # Kodi Helix
query = ' '.join(( query = '''
UPDATE album
"UPDATE album", SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?,
"SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", strImage = ?, iRating = ?, lastScraped = ?, dateAdded = ?,
"iRating = ?, lastScraped = ?, dateAdded = ?, " strLabel = ?
"strLabel = ? ", WHERE idAlbum = ?
"WHERE idAlbum = ?" '''
)) kodicursor.execute(query, (artistname, year, self.genre, bio,
kodicursor.execute(query, (artistname, year, genre, bio, thumb, thumb, rating, lastScraped, dateadded,
rating, lastScraped, dateadded, studio, studio, albumid))
albumid))
# Associate the parentid for plex reference # Associate the parentid for plex reference
parentId = item.attrib.get('parentRatingKey') parentId = item.attrib.get('parentRatingKey')
@ -1505,7 +1495,7 @@ class Music(Items):
artist = GetPlexMetadata(parentId) artist = GetPlexMetadata(parentId)
# Item may not be an artist, verification necessary. # Item may not be an artist, verification necessary.
if artist is not None and artist != 401: if artist is not None and artist != 401:
if artist[0].attrib.get('type') == "artist": if artist[0].attrib.get('type') == v.PLEX_TYPE_ARTIST:
# Update with the parentId, for remove reference # Update with the parentId, for remove reference
plex_db.addReference(parentId, plex_db.addReference(parentId,
v.PLEX_TYPE_ARTIST, v.PLEX_TYPE_ARTIST,
@ -1539,29 +1529,26 @@ class Music(Items):
% (artistname, artistid)) % (artistname, artistid))
# Add artist to album # Add artist to album
query = ( query = '''
'''
INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist)
VALUES (?, ?, ?) VALUES (?, ?, ?)
''' '''
)
kodicursor.execute(query, (artistid, albumid, artistname)) kodicursor.execute(query, (artistid, albumid, artistname))
# Update discography # Update discography
query = ( query = '''
'''
INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear)
VALUES (?, ?, ?) VALUES (?, ?, ?)
''' '''
)
kodicursor.execute(query, (artistid, name, year)) kodicursor.execute(query, (artistid, name, year))
# Update plex reference with parentid # Update plex reference with parentid
plex_db.updateParentId(artistId, albumid) plex_db.updateParentId(artistId, albumid)
# Add genres # Add genres
self.kodi_db.addMusicGenres(albumid, genres, "album") self.kodi_db.addMusicGenres(albumid, self.genres, v.KODI_TYPE_ALBUM)
# Update artwork # Update artwork
artwork.addArtwork(artworks, albumid, "album", kodicursor) artwork.addArtwork(artworks, albumid, v.KODI_TYPE_ALBUM, kodicursor)
# Add all children - all tracks
for child in children:
self.add_updateSong(child, viewtag, viewid)
@CatchExceptions(warnuser=True) @CatchExceptions(warnuser=True)
def add_updateSong(self, item, viewtag=None, viewid=None): def add_updateSong(self, item, viewtag=None, viewid=None):
@ -1601,9 +1588,22 @@ class Music(Items):
title, sorttitle = API.getTitle() title, sorttitle = API.getTitle()
# musicBrainzId = API.getProvider('MusicBrainzTrackId') # musicBrainzId = API.getProvider('MusicBrainzTrackId')
musicBrainzId = None musicBrainzId = None
genres = API.getGenres() try:
genre = API.joinList(genres) genres = self.genres
artists = item.attrib.get('grandparentTitle') genre = self.genre
except AttributeError:
# No parent album - hence no genre information from Plex
genres = None
genre = None
try:
if self.compilation == 0:
artists = item.attrib.get('grandparentTitle')
else:
artists = item.attrib.get('originalTitle')
except AttributeError:
# compilation not set
artists = item.attrib.get('originalTitle',
item.attrib.get('grandparentTitle'))
tracknumber = int(item.attrib.get('index', 0)) tracknumber = int(item.attrib.get('index', 0))
disc = int(item.attrib.get('parentIndex', 1)) disc = int(item.attrib.get('parentIndex', 1))
if disc == 1: if disc == 1:
@ -1613,9 +1613,13 @@ class Music(Items):
year = API.getYear() year = API.getYear()
resume, duration = API.getRuntime() resume, duration = API.getRuntime()
rating = userdata['UserRating'] rating = userdata['UserRating']
hasEmbeddedCover = False
comment = None comment = None
# Moods
moods = []
for entry in item:
if entry.tag == 'Mood':
moods.append(entry.attrib['tag'])
mood = ' / '.join(moods)
# GET THE FILE AND PATH ##### # GET THE FILE AND PATH #####
doIndirect = not self.directpath doIndirect = not self.directpath
@ -1653,16 +1657,18 @@ class Music(Items):
kodicursor.execute(query, (path, '123', pathid)) kodicursor.execute(query, (path, '123', pathid))
# Update the song entry # Update the song entry
query = ' '.join(( query = '''
"UPDATE song", UPDATE song
"SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?,", SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?,
"iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?,", iTrack = ?, iDuration = ?, iYear = ?, strFilename = ?,
"rating = ?, comment = ?", iTimesPlayed = ?, lastplayed = ?, rating = ?, comment = ?,
"WHERE idSong = ?" mood = ?
)) WHERE idSong = ?
'''
kodicursor.execute(query, (albumid, artists, genre, title, track, kodicursor.execute(query, (albumid, artists, genre, title, track,
duration, year, filename, playcount, duration, year, filename, playcount,
dateplayed, rating, comment, songid)) dateplayed, rating, comment, mood,
songid))
# Update the checksum in plex table # Update the checksum in plex table
plex_db.updateReference(itemid, checksum) plex_db.updateReference(itemid, checksum)
@ -1685,7 +1691,9 @@ class Music(Items):
if album_name: if album_name:
log.info("Creating virtual music album for song: %s." log.info("Creating virtual music album for song: %s."
% itemid) % itemid)
albumid = self.kodi_db.addAlbum(album_name, API.getProvider('MusicBrainzAlbum')) albumid = self.kodi_db.addAlbum(
album_name,
API.getProvider('MusicBrainzAlbum'))
plex_db.addReference("%salbum%s" % (itemid, albumid), plex_db.addReference("%salbum%s" % (itemid, albumid),
v.PLEX_TYPE_ALBUM, v.PLEX_TYPE_ALBUM,
albumid, albumid,
@ -1713,54 +1721,51 @@ class Music(Items):
except TypeError: except TypeError:
# No album found, create a single's album # No album found, create a single's album
log.info("Failed to add album. Creating singles.") log.info("Failed to add album. Creating singles.")
kodicursor.execute("select coalesce(max(idAlbum),0) from album") kodicursor.execute(
"select coalesce(max(idAlbum),0) from album")
albumid = kodicursor.fetchone()[0] + 1 albumid = kodicursor.fetchone()[0] + 1
if v.KODIVERSION >= 16: if v.KODIVERSION >= 16:
# Kodi Jarvis # Kodi Jarvis
query = ( query = '''
''' INSERT INTO album(
INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType) idAlbum, strGenres, iYear, strReleaseType)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
''' '''
) kodicursor.execute(query,
kodicursor.execute(query, (albumid, genre, year, "single")) (albumid, genre, year, "single"))
elif v.KODIVERSION == 15: elif v.KODIVERSION == 15:
# Kodi Isengard # Kodi Isengard
query = ( query = '''
''' INSERT INTO album(
INSERT INTO album(idAlbum, strGenres, iYear, dateAdded, strReleaseType) idAlbum, strGenres, iYear, dateAdded,
strReleaseType)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
''' '''
) kodicursor.execute(query, (albumid, genre, year,
kodicursor.execute(query, (albumid, genre, year, dateadded, "single")) dateadded, "single"))
else: else:
# Kodi Helix # Kodi Helix
query = ( query = '''
''' INSERT INTO album(
INSERT INTO album(idAlbum, strGenres, iYear, dateAdded) idAlbum, strGenres, iYear, dateAdded)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
''' '''
) kodicursor.execute(query, (albumid, genre, year,
kodicursor.execute(query, (albumid, genre, year, dateadded)) dateadded))
# Create the song entry # Create the song entry
query = ( query = '''
'''
INSERT INTO song( INSERT INTO song(
idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack, idSong, idAlbum, idPath, strArtists, strGenres, strTitle,
iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed, iTrack, iDuration, iYear, strFileName,
rating, iStartOffset, iEndOffset) strMusicBrainzTrackID, iTimesPlayed, lastplayed,
rating, iStartOffset, iEndOffset, mood)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''' '''
)
kodicursor.execute( kodicursor.execute(
query, (songid, albumid, pathid, artists, genre, title, track, query, (songid, albumid, pathid, artists, genre, title, track,
duration, year, filename, musicBrainzId, playcount, duration, year, filename, musicBrainzId, playcount,
dateplayed, rating, 0, 0)) dateplayed, rating, 0, 0, mood))
# Create the reference in plex table # Create the reference in plex table
plex_db.addReference(itemid, plex_db.addReference(itemid,
@ -1773,14 +1778,11 @@ class Music(Items):
view_id=viewid) view_id=viewid)
# Link song to album # Link song to album
query = ( query = '''
'''
INSERT OR REPLACE INTO albuminfosong( INSERT OR REPLACE INTO albuminfosong(
idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration) idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
''' '''
)
kodicursor.execute(query, (songid, albumid, track, title, duration)) kodicursor.execute(query, (songid, albumid, track, title, duration))
# Link song to artists # Link song to artists
@ -1808,29 +1810,27 @@ class Music(Items):
finally: finally:
if v.KODIVERSION >= 17: if v.KODIVERSION >= 17:
# Kodi Krypton # Kodi Krypton
query = ( query = '''
''' INSERT OR REPLACE INTO song_artist(
INSERT OR REPLACE INTO song_artist(idArtist, idSong, idRole, iOrder, strArtist) idArtist, idSong, idRole, iOrder, strArtist)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
''' '''
) kodicursor.execute(query, (artistid, songid, 1, index,
kodicursor.execute(query,(artistid, songid, 1, index, artist_name)) artist_name))
# May want to look into only doing this once? # May want to look into only doing this once?
query = ( query = '''
'''
INSERT OR REPLACE INTO role(idRole, strRole) INSERT OR REPLACE INTO role(idRole, strRole)
VALUES (?, ?) VALUES (?, ?)
''' '''
)
kodicursor.execute(query, (1, 'Composer')) kodicursor.execute(query, (1, 'Composer'))
else: else:
query = ( query = '''
''' INSERT OR REPLACE INTO song_artist(
INSERT OR REPLACE INTO song_artist(idArtist, idSong, iOrder, strArtist) idArtist, idSong, iOrder, strArtist)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
''' '''
) kodicursor.execute(query, (artistid, songid, index,
kodicursor.execute(query, (artistid, songid, index, artist_name)) artist_name))
# Verify if album artist exists # Verify if album artist exists
album_artists = [] album_artists = []
@ -1852,31 +1852,28 @@ class Music(Items):
artist_edb = plex_db.getItem_byId(artist_eid) artist_edb = plex_db.getItem_byId(artist_eid)
artistid = artist_edb[0] artistid = artist_edb[0]
finally: finally:
query = ( query = '''
''' INSERT OR REPLACE INTO album_artist(
INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) idArtist, idAlbum, strArtist)
VALUES (?, ?, ?) VALUES (?, ?, ?)
''' '''
)
kodicursor.execute(query, (artistid, albumid, artist_name)) kodicursor.execute(query, (artistid, albumid, artist_name))
# Update discography # Update discography
if item.get('Album'): if item.get('Album'):
query = ( query = '''
''' INSERT OR REPLACE INTO discography(
INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) idArtist, strAlbum, strYear)
VALUES (?, ?, ?) VALUES (?, ?, ?)
''' '''
)
kodicursor.execute(query, (artistid, item['Album'], 0)) kodicursor.execute(query, (artistid, item['Album'], 0))
# else: # else:
if False: if False:
album_artists = " / ".join(album_artists) album_artists = " / ".join(album_artists)
query = ' '.join(( query = '''
SELECT strArtists
"SELECT strArtists", FROM album
"FROM album", WHERE idAlbum = ?
"WHERE idAlbum = ?" '''
))
kodicursor.execute(query, (albumid,)) kodicursor.execute(query, (albumid,))
result = kodicursor.fetchone() result = kodicursor.fetchone()
if result and result[0] != album_artists: if result and result[0] != album_artists:
@ -1895,18 +1892,16 @@ class Music(Items):
kodicursor.execute(query, (album_artists, albumid)) kodicursor.execute(query, (album_artists, albumid))
# Add genres # Add genres
self.kodi_db.addMusicGenres(songid, genres, "song") if genres:
self.kodi_db.addMusicGenres(songid, genres, v.KODI_TYPE_SONG)
# Update artwork # Update artwork
allart = API.getAllArtwork(parentInfo=True) allart = API.getAllArtwork(parentInfo=True)
if hasEmbeddedCover: artwork.addArtwork(allart, songid, v.KODI_TYPE_SONG, kodicursor)
allart["Primary"] = "image://music@" + artwork.single_urlencode( playurl )
artwork.addArtwork(allart, songid, "song", kodicursor)
# if item.get('AlbumId') is None:
if item.get('parentKey') is None: if item.get('parentKey') is None:
# Update album artwork # Update album artwork
artwork.addArtwork(allart, albumid, "album", kodicursor) artwork.addArtwork(allart, albumid, v.KODI_TYPE_ALBUM, kodicursor)
def remove(self, itemid): def remove(self, itemid):
# Remove kodiid, fileid, pathid, plex reference # Remove kodiid, fileid, pathid, plex reference

View file

@ -0,0 +1 @@
# Dummy file to make this directory a package.

View file

@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from threading import Thread
from Queue import Empty
from xbmc import sleep
from utils import ThreadMethodsAdditionalStop, ThreadMethods, window, \
ThreadMethodsAdditionalSuspend
import plexdb_functions as plexdb
import itemtypes
import variables as v
###############################################################################
log = getLogger("PLEX."+__name__)
###############################################################################
@ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
@ThreadMethodsAdditionalStop('plex_shouldStop')
@ThreadMethods
class Process_Fanart_Thread(Thread):
"""
Threaded download of additional fanart in the background
Input:
queue Queue.Queue() object that you will need to fill with
dicts of the following form:
{
'plex_id': the Plex id as a string
'plex_type': the Plex media type, e.g. 'movie'
'refresh': True/False if True, will overwrite any 3rd party
fanart. If False, will only get missing
}
"""
def __init__(self, queue):
self.queue = queue
Thread.__init__(self)
def run(self):
"""
Catch all exceptions and log them
"""
try:
self.__run()
except Exception as e:
log.error('Exception %s' % e)
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
def __run(self):
"""
Do the work
"""
log.debug("---===### Starting FanartSync ###===---")
threadStopped = self.threadStopped
threadSuspended = self.threadSuspended
queue = self.queue
while not threadStopped():
# In the event the server goes offline
while threadSuspended() or window('plex_dbScan'):
# Set in service.py
if threadStopped():
# Abort was requested while waiting. We should exit
log.info("---===### Stopped FanartSync ###===---")
return
sleep(1000)
# grabs Plex item from queue
try:
item = queue.get(block=False)
except Empty:
sleep(200)
continue
log.debug('Get additional fanart for Plex id %s' % item['plex_id'])
with getattr(itemtypes,
v.ITEMTYPE_FROM_PLEXTYPE[item['plex_type']])() as cls:
result = cls.getfanart(item['plex_id'],
refresh=item['refresh'])
if result is True:
log.debug('Done getting fanart for Plex id %s'
% item['plex_id'])
with plexdb.Get_Plex_DB() as plex_db:
plex_db.set_fanart_synched(item['plex_id'])
queue.task_done()
log.debug("---===### Stopped FanartSync ###===---")

View file

@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from threading import Thread
from Queue import Empty
from xbmc import sleep
from utils import ThreadMethodsAdditionalStop, ThreadMethods, window
from PlexFunctions import GetPlexMetadata, GetAllPlexChildren
import sync_info
###############################################################################
log = getLogger("PLEX."+__name__)
###############################################################################
@ThreadMethodsAdditionalStop('suspend_LibraryThread')
@ThreadMethods
class Threaded_Get_Metadata(Thread):
"""
Threaded download of Plex XML metadata for a certain library item.
Fills the out_queue with the downloaded etree XML objects
Input:
queue Queue.Queue() object that you'll need to fill up
with Plex itemIds
out_queue Queue() object where this thread will store
the downloaded metadata XMLs as etree objects
"""
def __init__(self, queue, out_queue):
self.queue = queue
self.out_queue = out_queue
Thread.__init__(self)
def terminate_now(self):
"""
Needed to terminate this thread, because there might be items left in
the queue which could cause other threads to hang
"""
while not self.queue.empty():
# Still try because remaining item might have been taken
try:
self.queue.get(block=False)
except Empty:
sleep(10)
continue
else:
self.queue.task_done()
if self.threadStopped():
# Shutdown from outside requested; purge out_queue as well
while not self.out_queue.empty():
# Still try because remaining item might have been taken
try:
self.out_queue.get(block=False)
except Empty:
sleep(10)
continue
else:
self.out_queue.task_done()
def run(self):
"""
Catch all exceptions and log them
"""
try:
self.__run()
except Exception as e:
log.error('Exception %s' % e)
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
def __run(self):
"""
Do the work
"""
log.debug('Starting get metadata thread')
# cache local variables because it's faster
queue = self.queue
out_queue = self.out_queue
threadStopped = self.threadStopped
while threadStopped() is False:
# grabs Plex item from queue
try:
item = queue.get(block=False)
# Empty queue
except Empty:
sleep(20)
continue
# Download Metadata
xml = GetPlexMetadata(item['itemId'])
if xml is None:
# Did not receive a valid XML - skip that item for now
log.error("Could not get metadata for %s. Skipping that item "
"for now" % item['itemId'])
# Increase BOTH counters - since metadata won't be processed
with sync_info.LOCK:
sync_info.GET_METADATA_COUNT += 1
sync_info.PROCESS_METADATA_COUNT += 1
queue.task_done()
continue
elif xml == 401:
log.error('HTTP 401 returned by PMS. Too much strain? '
'Cancelling sync for now')
window('plex_scancrashed', value='401')
# Kill remaining items in queue (for main thread to cont.)
queue.task_done()
break
item['XML'] = xml
if item.get('get_children') is True:
children_xml = GetAllPlexChildren(item['itemId'])
try:
children_xml[0].attrib
except (TypeError, IndexError, AttributeError):
log.error('Could not get children for Plex id %s'
% item['itemId'])
else:
item['children'] = []
for child in children_xml:
child_xml = GetPlexMetadata(child.attrib['ratingKey'])
try:
child_xml[0].attrib
except (TypeError, IndexError, AttributeError):
log.error('Could not get child for Plex id %s'
% child.attrib['ratingKey'])
else:
item['children'].append(child_xml[0])
# place item into out queue
out_queue.put(item)
# Keep track of where we are at
with sync_info.LOCK:
sync_info.GET_METADATA_COUNT += 1
# signals to queue job is done
queue.task_done()
# Empty queue in case PKC was shut down (main thread hangs otherwise)
self.terminate_now()
log.debug('Get metadata thread terminated')

View file

@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from threading import Thread
from Queue import Empty
from xbmc import sleep
from utils import ThreadMethodsAdditionalStop, ThreadMethods
import itemtypes
import sync_info
###############################################################################
log = getLogger("PLEX."+__name__)
###############################################################################
@ThreadMethodsAdditionalStop('suspend_LibraryThread')
@ThreadMethods
class Threaded_Process_Metadata(Thread):
"""
Not yet implemented for more than 1 thread - if ever. Only to be called by
ONE thread!
Processes the XML metadata in the queue
Input:
queue: Queue.Queue() object that you'll need to fill up with
the downloaded XML eTree objects
item_type: as used to call functions in itemtypes.py e.g. 'Movies' =>
itemtypes.Movies()
"""
def __init__(self, queue, item_type):
self.queue = queue
self.item_type = item_type
Thread.__init__(self)
def terminate_now(self):
"""
Needed to terminate this thread, because there might be items left in
the queue which could cause other threads to hang
"""
while not self.queue.empty():
# Still try because remaining item might have been taken
try:
self.queue.get(block=False)
except Empty:
sleep(10)
continue
else:
self.queue.task_done()
def run(self):
"""
Catch all exceptions and log them
"""
try:
self.__run()
except Exception as e:
log.error('Exception %s' % e)
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
def __run(self):
"""
Do the work
"""
log.debug('Processing thread started')
# Constructs the method name, e.g. itemtypes.Movies
item_fct = getattr(itemtypes, self.item_type)
# cache local variables because it's faster
queue = self.queue
threadStopped = self.threadStopped
with item_fct() as item_class:
while threadStopped() is False:
# grabs item from queue
try:
item = queue.get(block=False)
except Empty:
sleep(20)
continue
# Do the work
item_method = getattr(item_class, item['method'])
if item.get('children') is not None:
item_method(item['XML'][0],
viewtag=item['viewName'],
viewid=item['viewId'],
children=item['children'])
else:
item_method(item['XML'][0],
viewtag=item['viewName'],
viewid=item['viewId'])
# Keep track of where we are at
try:
log.debug('found child: %s'
% item['children'].attrib)
except:
pass
with sync_info.LOCK:
sync_info.PROCESS_METADATA_COUNT += 1
sync_info.PROCESSING_VIEW_NAME = item['title']
queue.task_done()
self.terminate_now()
log.debug('Processing thread terminated')

View file

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from threading import Thread, Lock
from xbmc import sleep
from utils import ThreadMethodsAdditionalStop, ThreadMethods, language as lang
###############################################################################
log = getLogger("PLEX."+__name__)
GET_METADATA_COUNT = 0
PROCESS_METADATA_COUNT = 0
PROCESSING_VIEW_NAME = ''
LOCK = Lock()
###############################################################################
@ThreadMethodsAdditionalStop('suspend_LibraryThread')
@ThreadMethods
class Threaded_Show_Sync_Info(Thread):
"""
Threaded class to show the Kodi statusbar of the metadata download.
Input:
dialog xbmcgui.DialogProgressBG() object to show progress
total: Total number of items to get
"""
def __init__(self, dialog, total, item_type):
self.total = total
self.dialog = dialog
self.item_type = item_type
Thread.__init__(self)
def run(self):
"""
Catch all exceptions and log them
"""
try:
self.__run()
except Exception as e:
log.error('Exception %s' % e)
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
def __run(self):
"""
Do the work
"""
log.debug('Show sync info thread started')
# cache local variables because it's faster
total = self.total
dialog = self.dialog
threadStopped = self.threadStopped
dialog.create("%s: Sync %s: %s items"
% (lang(29999), self.item_type, str(total)),
"Starting")
total = 2 * total
totalProgress = 0
while threadStopped() is False:
with LOCK:
get_progress = GET_METADATA_COUNT
process_progress = PROCESS_METADATA_COUNT
viewName = PROCESSING_VIEW_NAME
totalProgress = get_progress + process_progress
try:
percentage = int(float(totalProgress) / float(total)*100.0)
except ZeroDivisionError:
percentage = 0
dialog.update(percentage,
message="%s downloaded. %s processed: %s"
% (get_progress,
process_progress,
viewName))
# Sleep for x milliseconds
sleep(200)
dialog.close()
log.debug('Show sync info thread terminated')

View file

@ -3,7 +3,7 @@
############################################################################### ###############################################################################
import logging import logging
from threading import Thread, Lock from threading import Thread
import Queue import Queue
from random import shuffle from random import shuffle
@ -28,6 +28,10 @@ import variables as v
from PlexFunctions import GetPlexMetadata, GetAllPlexLeaves, scrobble, \ from PlexFunctions import GetPlexMetadata, GetAllPlexLeaves, scrobble, \
GetPlexSectionResults, GetAllPlexChildren, GetPMSStatus GetPlexSectionResults, GetAllPlexChildren, GetPMSStatus
import PlexAPI import PlexAPI
from library_sync.get_metadata import Threaded_Get_Metadata
from library_sync.process_metadata import Threaded_Process_Metadata
import library_sync.sync_info as sync_info
from library_sync.fanart import Process_Fanart_Thread
############################################################################### ###############################################################################
@ -36,282 +40,6 @@ log = logging.getLogger("PLEX."+__name__)
############################################################################### ###############################################################################
@ThreadMethodsAdditionalStop('suspend_LibraryThread')
@ThreadMethods
class ThreadedGetMetadata(Thread):
"""
Threaded download of Plex XML metadata for a certain library item.
Fills the out_queue with the downloaded etree XML objects
Input:
queue Queue.Queue() object that you'll need to fill up
with Plex itemIds
out_queue Queue() object where this thread will store
the downloaded metadata XMLs as etree objects
lock Lock(), used for counting where we are
"""
def __init__(self, queue, out_queue, lock, processlock):
self.queue = queue
self.out_queue = out_queue
self.lock = lock
self.processlock = processlock
Thread.__init__(self)
def terminateNow(self):
while not self.queue.empty():
# Still try because remaining item might have been taken
try:
self.queue.get(block=False)
except Queue.Empty:
xbmc.sleep(10)
continue
else:
self.queue.task_done()
if self.threadStopped():
# Shutdown from outside requested; purge out_queue as well
while not self.out_queue.empty():
# Still try because remaining item might have been taken
try:
self.out_queue.get(block=False)
except Queue.Empty:
xbmc.sleep(10)
continue
else:
self.out_queue.task_done()
def run(self):
# cache local variables because it's faster
queue = self.queue
out_queue = self.out_queue
lock = self.lock
processlock = self.processlock
threadStopped = self.threadStopped
global getMetadataCount
global processMetadataCount
while threadStopped() is False:
# grabs Plex item from queue
try:
updateItem = queue.get(block=False)
# Empty queue
except Queue.Empty:
xbmc.sleep(10)
continue
# Download Metadata
plexXML = GetPlexMetadata(updateItem['itemId'])
if plexXML is None:
# Did not receive a valid XML - skip that item for now
log.warn("Could not get metadata for %s. Skipping that item "
"for now" % updateItem['itemId'])
# Increase BOTH counters - since metadata won't be processed
with lock:
getMetadataCount += 1
with processlock:
processMetadataCount += 1
queue.task_done()
continue
elif plexXML == 401:
log.warn('HTTP 401 returned by PMS. Too much strain? '
'Cancelling sync for now')
window('plex_scancrashed', value='401')
# Kill remaining items in queue (for main thread to cont.)
queue.task_done()
break
updateItem['XML'] = plexXML
# place item into out queue
out_queue.put(updateItem)
# Keep track of where we are at
with lock:
getMetadataCount += 1
# signals to queue job is done
queue.task_done()
# Empty queue in case PKC was shut down (main thread hangs otherwise)
self.terminateNow()
log.debug('Download thread terminated')
@ThreadMethodsAdditionalStop('suspend_LibraryThread')
@ThreadMethods
class ThreadedProcessMetadata(Thread):
"""
Not yet implemented - if ever. Only to be called by ONE thread!
Processes the XML metadata in the queue
Input:
queue: Queue.Queue() object that you'll need to fill up with
the downloaded XML eTree objects
itemType: as used to call functions in itemtypes.py
e.g. 'Movies' => itemtypes.Movies()
lock: Lock(), used for counting where we are
"""
def __init__(self, queue, itemType, lock):
self.queue = queue
self.lock = lock
self.itemType = itemType
Thread.__init__(self)
def terminateNow(self):
while not self.queue.empty():
# Still try because remaining item might have been taken
try:
self.queue.get(block=False)
except Queue.Empty:
xbmc.sleep(10)
continue
else:
self.queue.task_done()
def run(self):
# Constructs the method name, e.g. itemtypes.Movies
itemFkt = getattr(itemtypes, self.itemType)
# cache local variables because it's faster
queue = self.queue
lock = self.lock
threadStopped = self.threadStopped
global processMetadataCount
global processingViewName
with itemFkt() as item:
while threadStopped() is False:
# grabs item from queue
try:
updateItem = queue.get(block=False)
except Queue.Empty:
xbmc.sleep(10)
continue
# Do the work
plexitem = updateItem['XML']
method = updateItem['method']
viewName = updateItem['viewName']
viewId = updateItem['viewId']
title = updateItem['title']
itemSubFkt = getattr(item, method)
# Get the one child entry in the xml and process
for child in plexitem:
itemSubFkt(child,
viewtag=viewName,
viewid=viewId)
# Keep track of where we are at
with lock:
processMetadataCount += 1
processingViewName = title
# signals to queue job is done
queue.task_done()
# Empty queue in case PKC was shut down (main thread hangs otherwise)
self.terminateNow()
log.debug('Processing thread terminated')
@ThreadMethodsAdditionalStop('suspend_LibraryThread')
@ThreadMethods
class ThreadedShowSyncInfo(Thread):
"""
Threaded class to show the Kodi statusbar of the metadata download.
Input:
dialog xbmcgui.DialogProgressBG() object to show progress
locks = [downloadLock, processLock] Locks() to the other threads
total: Total number of items to get
"""
def __init__(self, dialog, locks, total, itemType):
self.locks = locks
self.total = total
self.dialog = dialog
self.itemType = itemType
Thread.__init__(self)
def run(self):
# cache local variables because it's faster
total = self.total
dialog = self.dialog
threadStopped = self.threadStopped
downloadLock = self.locks[0]
processLock = self.locks[1]
dialog.create("%s: Sync %s: %s items"
% (lang(29999), self.itemType, str(total)),
"Starting")
global getMetadataCount
global processMetadataCount
global processingViewName
total = 2 * total
totalProgress = 0
while threadStopped() is False:
with downloadLock:
getMetadataProgress = getMetadataCount
with processLock:
processMetadataProgress = processMetadataCount
viewName = processingViewName
totalProgress = getMetadataProgress + processMetadataProgress
try:
percentage = int(float(totalProgress) / float(total)*100.0)
except ZeroDivisionError:
percentage = 0
dialog.update(percentage,
message="%s downloaded. %s processed: %s"
% (getMetadataProgress,
processMetadataProgress,
viewName))
# Sleep for x milliseconds
xbmc.sleep(200)
dialog.close()
log.debug('Dialog Infobox thread terminated')
@ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
@ThreadMethodsAdditionalStop('plex_shouldStop')
@ThreadMethods
class ProcessFanartThread(Thread):
"""
Threaded download of additional fanart in the background
Input:
queue Queue.Queue() object that you will need to fill with
dicts of the following form:
{
'plex_id': the Plex id as a string
'plex_type': the Plex media type, e.g. 'movie'
'refresh': True/False if True, will overwrite any 3rd party
fanart. If False, will only get missing
}
"""
def __init__(self, queue):
self.queue = queue
Thread.__init__(self)
def run(self):
threadStopped = self.threadStopped
threadSuspended = self.threadSuspended
queue = self.queue
log.info("---===### Starting FanartSync ###===---")
while not threadStopped():
# In the event the server goes offline
while threadSuspended() or window('plex_dbScan'):
# Set in service.py
if threadStopped():
# Abort was requested while waiting. We should exit
log.info("---===### Stopped FanartSync ###===---")
return
xbmc.sleep(1000)
# grabs Plex item from queue
try:
item = queue.get(block=False)
except Queue.Empty:
xbmc.sleep(200)
continue
log.debug('Get additional fanart for Plex id %s' % item['plex_id'])
with getattr(itemtypes,
v.ITEMTYPE_FROM_PLEXTYPE[item['plex_type']])() as cls:
result = cls.getfanart(item['plex_id'],
refresh=item['refresh'])
if result is True:
log.debug('Done getting fanart for Plex id %s'
% item['plex_id'])
with plexdb.Get_Plex_DB() as plex_db:
plex_db.set_fanart_synched(item['plex_id'])
queue.task_done()
log.info("---===### Stopped FanartSync ###===---")
@ThreadMethodsAdditionalSuspend('suspend_LibraryThread') @ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
@ThreadMethodsAdditionalStop('plex_shouldStop') @ThreadMethodsAdditionalStop('plex_shouldStop')
@ThreadMethods @ThreadMethods
@ -330,7 +58,7 @@ class LibrarySync(Thread):
self.sessionKeys = [] self.sessionKeys = []
self.fanartqueue = Queue.Queue() self.fanartqueue = Queue.Queue()
if settings('FanartTV') == 'true': if settings('FanartTV') == 'true':
self.fanartthread = ProcessFanartThread(self.fanartqueue) self.fanartthread = Process_Fanart_Thread(self.fanartqueue)
# How long should we wait at least to process new/changed PMS items? # How long should we wait at least to process new/changed PMS items?
self.saftyMargin = int(settings('backgroundsync_saftyMargin')) self.saftyMargin = int(settings('backgroundsync_saftyMargin'))
@ -346,7 +74,6 @@ class LibrarySync(Thread):
self.enableMusic = settings('enableMusic') == "true" self.enableMusic = settings('enableMusic') == "true"
self.enableBackgroundSync = settings( self.enableBackgroundSync = settings(
'enableBackgroundSync') == "true" 'enableBackgroundSync') == "true"
self.limitindex = int(settings('limitindex'))
# Init for replacing paths # Init for replacing paths
window('remapSMB', value=settings('remapSMB')) window('remapSMB', value=settings('remapSMB'))
@ -422,8 +149,7 @@ class LibrarySync(Thread):
if not view.attrib['type'] == mediatype: if not view.attrib['type'] == mediatype:
continue continue
libraryId = view.attrib['key'] libraryId = view.attrib['key']
items = GetAllPlexLeaves(libraryId, items = GetAllPlexLeaves(libraryId)
containerSize=self.limitindex)
if items in (None, 401): if items in (None, 401):
log.error("Could not download section %s" log.error("Could not download section %s"
% view.attrib['key']) % view.attrib['key'])
@ -468,9 +194,7 @@ class LibrarySync(Thread):
# Let the PMS process this first! # Let the PMS process this first!
xbmc.sleep(1000) xbmc.sleep(1000)
# Get PMS items to find the item we just changed # Get PMS items to find the item we just changed
items = GetAllPlexLeaves(libraryId, items = GetAllPlexLeaves(libraryId, lastViewedAt=timestamp)
lastViewedAt=timestamp,
containerSize=self.limitindex)
# Toggle watched state back # Toggle watched state back
scrobble(plexId, 'unwatched') scrobble(plexId, 'unwatched')
if items in (None, 401): if items in (None, 401):
@ -704,8 +428,8 @@ class LibrarySync(Thread):
viewid=folderid, viewid=folderid,
delete=True) delete=True)
# Added new playlist # Added new playlist
if (foldername not in playlists and if (foldername not in playlists and mediatype in
mediatype in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)): (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
playlistXSP(mediatype, playlistXSP(mediatype,
foldername, foldername,
folderid, folderid,
@ -730,8 +454,8 @@ class LibrarySync(Thread):
else: else:
# Validate the playlist exists or recreate it # Validate the playlist exists or recreate it
if mediatype != v.PLEX_TYPE_ARTIST: if mediatype != v.PLEX_TYPE_ARTIST:
if (foldername not in playlists and if (foldername not in playlists and mediatype in
mediatype in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)): (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
playlistXSP(mediatype, playlistXSP(mediatype,
foldername, foldername,
folderid, folderid,
@ -781,7 +505,8 @@ class LibrarySync(Thread):
for view in sections: for view in sections:
itemType = view.attrib['type'] itemType = view.attrib['type']
if itemType in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW, v.PLEX_TYPE_PHOTO): # NOT artist for now if (itemType in
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW, v.PLEX_TYPE_PHOTO)):
self.sorted_views.append(view.attrib['title']) self.sorted_views.append(view.attrib['title'])
log.debug('Sorted views: %s' % self.sorted_views) log.debug('Sorted views: %s' % self.sorted_views)
@ -863,7 +588,8 @@ class LibrarySync(Thread):
with itemtypes.Music() as music: with itemtypes.Music() as music:
music.remove(item['plex_id']) music.remove(item['plex_id'])
def GetUpdatelist(self, xml, itemType, method, viewName, viewId): def GetUpdatelist(self, xml, itemType, method, viewName, viewId,
get_children=False):
""" """
THIS METHOD NEEDS TO BE FAST! => e.g. no API calls THIS METHOD NEEDS TO BE FAST! => e.g. no API calls
@ -876,6 +602,8 @@ class LibrarySync(Thread):
see itemtypes.py see itemtypes.py
viewName: Name of the Plex view (e.g. 'My TV shows') viewName: Name of the Plex view (e.g. 'My TV shows')
viewId: Id/Key of Plex library (e.g. '1') viewId: Id/Key of Plex library (e.g. '1')
get_children: will get Plex children of the item if True,
e.g. for music albums
Output: self.updatelist, self.allPlexElementsId Output: self.updatelist, self.allPlexElementsId
self.updatelist APPENDED(!!) list itemids (Plex Keys as self.updatelist APPENDED(!!) list itemids (Plex Keys as
@ -910,7 +638,8 @@ class LibrarySync(Thread):
'viewName': viewName, 'viewName': viewName,
'viewId': viewId, 'viewId': viewId,
'title': item.attrib.get('title', 'Missing Title'), 'title': item.attrib.get('title', 'Missing Title'),
'mediaType': item.attrib.get('type') 'mediaType': item.attrib.get('type'),
'get_children': get_children
}) })
self.just_processed[itemId] = now self.just_processed[itemId] = now
return return
@ -936,7 +665,8 @@ class LibrarySync(Thread):
'viewName': viewName, 'viewName': viewName,
'viewId': viewId, 'viewId': viewId,
'title': item.attrib.get('title', 'Missing Title'), 'title': item.attrib.get('title', 'Missing Title'),
'mediaType': item.attrib.get('type') 'mediaType': item.attrib.get('type'),
'get_children': get_children
}) })
self.just_processed[itemId] = now self.just_processed[itemId] = now
else: else:
@ -955,7 +685,8 @@ class LibrarySync(Thread):
'viewName': viewName, 'viewName': viewName,
'viewId': viewId, 'viewId': viewId,
'title': item.attrib.get('title', 'Missing Title'), 'title': item.attrib.get('title', 'Missing Title'),
'mediaType': item.attrib.get('type') 'mediaType': item.attrib.get('type'),
'get_children': get_children
}) })
self.just_processed[itemId] = now self.just_processed[itemId] = now
@ -980,49 +711,38 @@ class LibrarySync(Thread):
log.info("Starting sync threads") log.info("Starting sync threads")
getMetadataQueue = Queue.Queue() getMetadataQueue = Queue.Queue()
processMetadataQueue = Queue.Queue(maxsize=100) processMetadataQueue = Queue.Queue(maxsize=100)
getMetadataLock = Lock()
processMetadataLock = Lock()
# To keep track # To keep track
global getMetadataCount sync_info.GET_METADATA_COUNT = 0
getMetadataCount = 0 sync_info.PROCESS_METADATA_COUNT = 0
global processMetadataCount sync_info.PROCESSING_VIEW_NAME = ''
processMetadataCount = 0
global processingViewName
processingViewName = ''
# Populate queue: GetMetadata # Populate queue: GetMetadata
for updateItem in self.updatelist: for updateItem in self.updatelist:
getMetadataQueue.put(updateItem) getMetadataQueue.put(updateItem)
# Spawn GetMetadata threads for downloading # Spawn GetMetadata threads for downloading
threads = [] threads = []
for i in range(min(self.syncThreadNumber, itemNumber)): for i in range(min(self.syncThreadNumber, itemNumber)):
thread = ThreadedGetMetadata(getMetadataQueue, thread = Threaded_Get_Metadata(getMetadataQueue,
processMetadataQueue, processMetadataQueue)
getMetadataLock,
processMetadataLock)
thread.setDaemon(True) thread.setDaemon(True)
thread.start() thread.start()
threads.append(thread) threads.append(thread)
log.info("%s download threads spawned" % len(threads)) log.info("%s download threads spawned" % len(threads))
# Spawn one more thread to process Metadata, once downloaded # Spawn one more thread to process Metadata, once downloaded
thread = ThreadedProcessMetadata(processMetadataQueue, thread = Threaded_Process_Metadata(processMetadataQueue,
itemType, itemType)
processMetadataLock)
thread.setDaemon(True) thread.setDaemon(True)
thread.start() thread.start()
threads.append(thread) threads.append(thread)
log.info("Processing thread spawned")
# Start one thread to show sync progress ONLY for new PMS items # Start one thread to show sync progress ONLY for new PMS items
if self.new_items_only is True and window('dbSyncIndicator') == 'true': if self.new_items_only is True and window('dbSyncIndicator') == 'true':
dialog = xbmcgui.DialogProgressBG() dialog = xbmcgui.DialogProgressBG()
thread = ThreadedShowSyncInfo( thread = sync_info.Threaded_Show_Sync_Info(
dialog, dialog,
[getMetadataLock, processMetadataLock],
itemNumber, itemNumber,
itemType) itemType)
thread.setDaemon(True) thread.setDaemon(True)
thread.start() thread.start()
threads.append(thread) threads.append(thread)
log.info("Kodi Infobox thread spawned")
# Wait until finished # Wait until finished
getMetadataQueue.join() getMetadataQueue.join()
@ -1083,8 +803,7 @@ class LibrarySync(Thread):
# Get items per view # Get items per view
viewId = view['id'] viewId = view['id']
viewName = view['name'] viewName = view['name']
all_plexmovies = GetPlexSectionResults( all_plexmovies = GetPlexSectionResults(viewId, args=None)
viewId, args=None, containerSize=self.limitindex)
if all_plexmovies is None: if all_plexmovies is None:
log.info("Couldnt get section items, aborting for view.") log.info("Couldnt get section items, aborting for view.")
continue continue
@ -1127,8 +846,7 @@ class LibrarySync(Thread):
return return
xml = GetAllPlexLeaves(viewId, xml = GetAllPlexLeaves(viewId,
lastViewedAt=lastViewedAt, lastViewedAt=lastViewedAt,
updatedAt=updatedAt, updatedAt=updatedAt)
containerSize=self.limitindex)
# Return if there are no items in PMS reply - it's faster # Return if there are no items in PMS reply - it's faster
try: try:
xml[0].attrib xml[0].attrib
@ -1178,8 +896,7 @@ class LibrarySync(Thread):
# Get items per view # Get items per view
viewId = view['id'] viewId = view['id']
viewName = view['name'] viewName = view['name']
allPlexTvShows = GetPlexSectionResults( allPlexTvShows = GetPlexSectionResults(viewId)
viewId, containerSize=self.limitindex)
if allPlexTvShows is None: if allPlexTvShows is None:
log.error("Error downloading show xml for view %s" % viewId) log.error("Error downloading show xml for view %s" % viewId)
continue continue
@ -1206,8 +923,7 @@ class LibrarySync(Thread):
if self.threadStopped(): if self.threadStopped():
return False return False
# Grab all seasons to tvshow from PMS # Grab all seasons to tvshow from PMS
seasons = GetAllPlexChildren( seasons = GetAllPlexChildren(tvShowId)
tvShowId, containerSize=self.limitindex)
if seasons is None: if seasons is None:
log.error("Error download season xml for show %s" % tvShowId) log.error("Error download season xml for show %s" % tvShowId)
continue continue
@ -1232,8 +948,7 @@ class LibrarySync(Thread):
if self.threadStopped(): if self.threadStopped():
return False return False
# Grab all episodes to tvshow from PMS # Grab all episodes to tvshow from PMS
episodes = GetAllPlexLeaves( episodes = GetAllPlexLeaves(view['id'])
view['id'], containerSize=self.limitindex)
if episodes is None: if episodes is None:
log.error("Error downloading episod xml for view %s" log.error("Error downloading episod xml for view %s"
% view.get('name')) % view.get('name'))
@ -1297,12 +1012,17 @@ class LibrarySync(Thread):
} }
# Process artist, then album and tracks last to minimize overhead # Process artist, then album and tracks last to minimize overhead
# Each album needs to be processed directly with its songs
# Remaining songs without album will be processed last
for kind in (v.PLEX_TYPE_ARTIST, for kind in (v.PLEX_TYPE_ARTIST,
v.PLEX_TYPE_ALBUM, v.PLEX_TYPE_ALBUM,
v.PLEX_TYPE_SONG): v.PLEX_TYPE_SONG):
if self.threadStopped(): if self.threadStopped():
return False return False
log.debug("Start processing music %s" % kind) log.debug("Start processing music %s" % kind)
self.allKodiElementsId = {}
self.allPlexElementsId = {}
self.updatelist = []
if self.ProcessMusic(views, if self.ProcessMusic(views,
kind, kind,
urlArgs[kind], urlArgs[kind],
@ -1326,10 +1046,8 @@ class LibrarySync(Thread):
return True return True
def ProcessMusic(self, views, kind, urlArgs, method): def ProcessMusic(self, views, kind, urlArgs, method):
self.allKodiElementsId = {} # For albums, we need to look at the album's songs simultaneously
self.allPlexElementsId = {} get_children = True if kind == v.PLEX_TYPE_ALBUM else False
self.updatelist = []
# Get a list of items already existing in Kodi db # Get a list of items already existing in Kodi db
if self.compare: if self.compare:
with plexdb.Get_Plex_DB() as plex_db: with plexdb.Get_Plex_DB() as plex_db:
@ -1340,17 +1058,13 @@ class LibrarySync(Thread):
# Yet empty/nothing yet synched # Yet empty/nothing yet synched
except ValueError: except ValueError:
pass pass
for view in views: for view in views:
if self.threadStopped(): if self.threadStopped():
return False return False
# Get items per view # Get items per view
viewId = view['id'] itemsXML = GetPlexSectionResults(view['id'], args=urlArgs)
viewName = view['name']
itemsXML = GetPlexSectionResults(
viewId, args=urlArgs, containerSize=self.limitindex)
if itemsXML is None: if itemsXML is None:
log.error("Error downloading xml for view %s" % viewId) log.error("Error downloading xml for view %s" % view['id'])
continue continue
elif itemsXML == 401: elif itemsXML == 401:
return False return False
@ -1358,9 +1072,9 @@ class LibrarySync(Thread):
self.GetUpdatelist(itemsXML, self.GetUpdatelist(itemsXML,
'Music', 'Music',
method, method,
viewName, view['name'],
viewId) view['id'],
get_children=get_children)
if self.compare: if self.compare:
# Manual sync, process deletes # Manual sync, process deletes
with itemtypes.Music() as Music: with itemtypes.Music() as Music: