Merge branch 'develop' into translations
This commit is contained in:
commit
f8fe6b6659
10 changed files with 693 additions and 563 deletions
|
@ -39,6 +39,7 @@ import xml.etree.ElementTree as etree
|
|||
from re import compile as re_compile, sub
|
||||
from json import dumps
|
||||
from urllib import urlencode, quote_plus, unquote
|
||||
from os import path as os_path
|
||||
|
||||
import xbmcgui
|
||||
from xbmc import sleep, executebuiltin
|
||||
|
@ -2186,13 +2187,38 @@ class API():
|
|||
# Several streams/files available.
|
||||
dialoglist = []
|
||||
for entry in self.item.findall('./Media'):
|
||||
dialoglist.append(
|
||||
"%sp %s - %s (%s)"
|
||||
% (entry.attrib.get('videoResolution', 'unknown'),
|
||||
entry.attrib.get('videoCodec', 'unknown'),
|
||||
entry.attrib.get('audioProfile', 'unknown'),
|
||||
entry.attrib.get('audioCodec', 'unknown'))
|
||||
)
|
||||
# Get additional info (filename / languages)
|
||||
filename = None
|
||||
if 'file' in entry[0].attrib:
|
||||
filename = os_path.basename(entry[0].attrib['file'])
|
||||
# Languages of audio streams
|
||||
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)
|
||||
else:
|
||||
media = 0
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from logging import getLogger
|
||||
from urllib import urlencode
|
||||
from ast import literal_eval
|
||||
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
|
||||
|
||||
|
||||
def GetAllPlexChildren(key, containerSize=None):
|
||||
def GetAllPlexChildren(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)
|
||||
|
@ -149,11 +151,10 @@ def GetAllPlexChildren(key, containerSize=None):
|
|||
Input:
|
||||
key Key to a Plex item, e.g. 12345
|
||||
"""
|
||||
url = "{server}/library/metadata/%s/children?" % key
|
||||
return DownloadChunks(url, containerSize)
|
||||
return DownloadChunks("{server}/library/metadata/%s/children?" % key)
|
||||
|
||||
|
||||
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
|
||||
section with key = viewId.
|
||||
|
@ -166,38 +167,23 @@ def GetPlexSectionResults(viewId, args=None, containerSize=None):
|
|||
url = "{server}/library/sections/%s/all?" % viewId
|
||||
if 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).
|
||||
If containerSize is None: ONE xml is fetched directly
|
||||
Downloads PMS url in chunks of CONTAINERSIZE.
|
||||
|
||||
url MUST end with '?' (if no other url encoded args are present) or '&'
|
||||
|
||||
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
|
||||
pos = 0
|
||||
errorCounter = 0
|
||||
while errorCounter < 10:
|
||||
args = {
|
||||
'X-Plex-Container-Size': containerSize,
|
||||
'X-Plex-Container-Size': CONTAINERSIZE,
|
||||
'X-Plex-Container-Start': pos
|
||||
}
|
||||
xmlpart = downloadutils.DownloadUtils().downloadUrl(
|
||||
|
@ -208,33 +194,32 @@ def DownloadChunks(url, containerSize):
|
|||
except AttributeError:
|
||||
log.error('Error while downloading chunks: %s'
|
||||
% (url + urlencode(args)))
|
||||
pos += containerSize
|
||||
pos += CONTAINERSIZE
|
||||
errorCounter += 1
|
||||
continue
|
||||
|
||||
# Very first run: starting xml (to retain data in xml's root!)
|
||||
if xml is None:
|
||||
xml = deepcopy(xmlpart)
|
||||
if len(xmlpart) < containerSize:
|
||||
if len(xmlpart) < CONTAINERSIZE:
|
||||
break
|
||||
else:
|
||||
pos += containerSize
|
||||
pos += CONTAINERSIZE
|
||||
continue
|
||||
# Build answer xml - containing the entire library
|
||||
for child in xmlpart:
|
||||
xml.append(child)
|
||||
# Done as soon as we don't receive a full complement of items
|
||||
if len(xmlpart) < containerSize:
|
||||
if len(xmlpart) < CONTAINERSIZE:
|
||||
break
|
||||
pos += containerSize
|
||||
pos += CONTAINERSIZE
|
||||
if errorCounter == 10:
|
||||
log.error('Fatal error while downloading chunks for %s' % url)
|
||||
return None
|
||||
return xml
|
||||
|
||||
|
||||
def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None,
|
||||
containerSize=None):
|
||||
def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None):
|
||||
"""
|
||||
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)
|
||||
|
@ -245,7 +230,6 @@ def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None,
|
|||
since that point of time until now.
|
||||
updatedAt Unix timestamp; only retrieves PMS items updated
|
||||
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.
|
||||
|
||||
|
@ -265,14 +249,13 @@ def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None,
|
|||
url += '?' + '&'.join(args) + '&'
|
||||
else:
|
||||
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(url, containerSize)
|
||||
return DownloadChunks("{server}/library/sections/%s/onDeck?" % viewId)
|
||||
|
||||
|
||||
def GetPlexCollections(mediatype):
|
||||
|
|
|
@ -791,9 +791,7 @@ def browse_plex(key=None, plex_section_id=None):
|
|||
if key:
|
||||
xml = downloadutils.DownloadUtils().downloadUrl('{server}%s' % key)
|
||||
else:
|
||||
xml = GetPlexSectionResults(
|
||||
plex_section_id,
|
||||
containerSize=int(settings('limitindex')))
|
||||
xml = GetPlexSectionResults(plex_section_id)
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (ValueError, AttributeError, IndexError, TypeError):
|
||||
|
|
|
@ -9,8 +9,7 @@ from datetime import datetime
|
|||
from xbmc import sleep
|
||||
|
||||
import artwork
|
||||
from utils import tryEncode, tryDecode, settings, window, kodiSQL, \
|
||||
CatchExceptions
|
||||
from utils import tryEncode, tryDecode, window, kodiSQL, CatchExceptions
|
||||
import plexdb_functions as plexdb
|
||||
import kodidb_functions as kodidb
|
||||
|
||||
|
@ -1259,14 +1258,6 @@ class TVShows(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):
|
||||
"""
|
||||
OVERWRITE this method, because we need to open another DB.
|
||||
|
@ -1305,7 +1296,7 @@ class Music(Items):
|
|||
name, sortname = API.getTitle()
|
||||
# musicBrainzId = API.getProvider('MusicBrainzArtist')
|
||||
musicBrainzId = None
|
||||
genres = API.joinList(API.getGenres())
|
||||
genres = ' / '.join(API.getGenres())
|
||||
bio = API.getPlot()
|
||||
|
||||
# Associate artwork
|
||||
|
@ -1344,31 +1335,32 @@ class Music(Items):
|
|||
|
||||
# Process the artist
|
||||
if v.KODIVERSION >= 16:
|
||||
query = ' '.join((
|
||||
|
||||
"UPDATE artist",
|
||||
"SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,",
|
||||
"lastScraped = ?",
|
||||
"WHERE idArtist = ?"
|
||||
))
|
||||
query = '''
|
||||
UPDATE artist
|
||||
SET strGenres = ?, strBiography = ?, strImage = ?,
|
||||
strFanart = ?, lastScraped = ?
|
||||
WHERE idArtist = ?
|
||||
'''
|
||||
kodicursor.execute(query, (genres, bio, thumb, fanart,
|
||||
lastScraped, artistid))
|
||||
else:
|
||||
query = ' '.join((
|
||||
|
||||
"UPDATE artist",
|
||||
"SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,",
|
||||
"lastScraped = ?, dateAdded = ?",
|
||||
"WHERE idArtist = ?"
|
||||
))
|
||||
query = '''
|
||||
UPDATE artist
|
||||
SET strGenres = ?, strBiography = ?, strImage = ?,
|
||||
strFanart = ?, lastScraped = ?, dateAdded = ?
|
||||
WHERE idArtist = ?
|
||||
'''
|
||||
kodicursor.execute(query, (genres, bio, thumb, fanart, lastScraped,
|
||||
dateadded, artistid))
|
||||
|
||||
# Update artwork
|
||||
artwork.addArtwork(artworks, artistid, "artist", kodicursor)
|
||||
artwork.addArtwork(artworks, artistid, v.KODI_TYPE_ARTIST, kodicursor)
|
||||
|
||||
@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
|
||||
plex_db = self.plex_db
|
||||
artwork = self.artwork
|
||||
|
@ -1396,21 +1388,21 @@ class Music(Items):
|
|||
# musicBrainzId = API.getProvider('MusicBrainzAlbum')
|
||||
musicBrainzId = None
|
||||
year = API.getYear()
|
||||
genres = API.getGenres()
|
||||
genre = API.joinList(genres)
|
||||
self.genres = API.getGenres()
|
||||
self.genre = ' / '.join(self.genres)
|
||||
bio = API.getPlot()
|
||||
rating = userdata['UserRating']
|
||||
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')
|
||||
if not artistname:
|
||||
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
|
||||
artworks = API.getAllArtwork(parentInfo=True)
|
||||
thumb = artworks['Primary']
|
||||
|
@ -1442,56 +1434,54 @@ class Music(Items):
|
|||
# Process the album info
|
||||
if v.KODIVERSION >= 17:
|
||||
# Kodi Krypton
|
||||
query = ' '.join((
|
||||
|
||||
"UPDATE album",
|
||||
"SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
|
||||
"iUserrating = ?, lastScraped = ?, strReleaseType = ?, "
|
||||
"strLabel = ? ",
|
||||
"WHERE idAlbum = ?"
|
||||
))
|
||||
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
|
||||
rating, lastScraped, "album", studio,
|
||||
albumid))
|
||||
query = '''
|
||||
UPDATE album
|
||||
SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?,
|
||||
strImage = ?, iUserrating = ?, lastScraped = ?,
|
||||
strReleaseType = ?, strLabel = ?, bCompilation = ?
|
||||
WHERE idAlbum = ?
|
||||
'''
|
||||
kodicursor.execute(query, (artistname, year, self.genre, bio,
|
||||
thumb, rating, lastScraped,
|
||||
v.KODI_TYPE_ALBUM, studio,
|
||||
self.compilation, albumid))
|
||||
elif v.KODIVERSION == 16:
|
||||
# Kodi Jarvis
|
||||
query = ' '.join((
|
||||
|
||||
"UPDATE album",
|
||||
"SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
|
||||
"iRating = ?, lastScraped = ?, strReleaseType = ?, "
|
||||
"strLabel = ? ",
|
||||
"WHERE idAlbum = ?"
|
||||
))
|
||||
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
|
||||
rating, lastScraped, "album", studio,
|
||||
albumid))
|
||||
query = '''
|
||||
UPDATE album
|
||||
SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?,
|
||||
strImage = ?, iRating = ?, lastScraped = ?,
|
||||
strReleaseType = ?, strLabel = ?, bCompilation = ?
|
||||
WHERE idAlbum = ?
|
||||
'''
|
||||
kodicursor.execute(query, (artistname, year, self.genre, bio,
|
||||
thumb, rating, lastScraped,
|
||||
v.KODI_TYPE_ALBUM, studio,
|
||||
self.compilation, albumid))
|
||||
elif v.KODIVERSION == 15:
|
||||
# Kodi Isengard
|
||||
query = ' '.join((
|
||||
|
||||
"UPDATE album",
|
||||
"SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
|
||||
"iRating = ?, lastScraped = ?, dateAdded = ?, "
|
||||
"strReleaseType = ?, strLabel = ? ",
|
||||
"WHERE idAlbum = ?"
|
||||
))
|
||||
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
|
||||
rating, lastScraped, dateadded,
|
||||
"album", studio, albumid))
|
||||
query = '''
|
||||
UPDATE album
|
||||
SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?,
|
||||
strImage = ?, iRating = ?, lastScraped = ?, dateAdded = ?,
|
||||
strReleaseType = ?, strLabel = ?
|
||||
WHERE idAlbum = ?
|
||||
'''
|
||||
kodicursor.execute(query, (artistname, year, self.genre, bio,
|
||||
thumb, rating, lastScraped, dateadded,
|
||||
v.KODI_TYPE_ALBUM, studio, albumid))
|
||||
else:
|
||||
# Kodi Helix
|
||||
query = ' '.join((
|
||||
|
||||
"UPDATE album",
|
||||
"SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
|
||||
"iRating = ?, lastScraped = ?, dateAdded = ?, "
|
||||
"strLabel = ? ",
|
||||
"WHERE idAlbum = ?"
|
||||
))
|
||||
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
|
||||
rating, lastScraped, dateadded, studio,
|
||||
albumid))
|
||||
query = '''
|
||||
UPDATE album
|
||||
SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?,
|
||||
strImage = ?, iRating = ?, lastScraped = ?, dateAdded = ?,
|
||||
strLabel = ?
|
||||
WHERE idAlbum = ?
|
||||
'''
|
||||
kodicursor.execute(query, (artistname, year, self.genre, bio,
|
||||
thumb, rating, lastScraped, dateadded,
|
||||
studio, albumid))
|
||||
|
||||
# Associate the parentid for plex reference
|
||||
parentId = item.attrib.get('parentRatingKey')
|
||||
|
@ -1505,7 +1495,7 @@ class Music(Items):
|
|||
artist = GetPlexMetadata(parentId)
|
||||
# Item may not be an artist, verification necessary.
|
||||
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
|
||||
plex_db.addReference(parentId,
|
||||
v.PLEX_TYPE_ARTIST,
|
||||
|
@ -1539,29 +1529,26 @@ class Music(Items):
|
|||
% (artistname, artistid))
|
||||
|
||||
# Add artist to album
|
||||
query = (
|
||||
'''
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist)
|
||||
|
||||
VALUES (?, ?, ?)
|
||||
'''
|
||||
)
|
||||
kodicursor.execute(query, (artistid, albumid, artistname))
|
||||
# Update discography
|
||||
query = (
|
||||
'''
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear)
|
||||
|
||||
VALUES (?, ?, ?)
|
||||
'''
|
||||
)
|
||||
kodicursor.execute(query, (artistid, name, year))
|
||||
# Update plex reference with parentid
|
||||
plex_db.updateParentId(artistId, albumid)
|
||||
# Add genres
|
||||
self.kodi_db.addMusicGenres(albumid, genres, "album")
|
||||
self.kodi_db.addMusicGenres(albumid, self.genres, v.KODI_TYPE_ALBUM)
|
||||
# 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)
|
||||
def add_updateSong(self, item, viewtag=None, viewid=None):
|
||||
|
@ -1601,9 +1588,22 @@ class Music(Items):
|
|||
title, sorttitle = API.getTitle()
|
||||
# musicBrainzId = API.getProvider('MusicBrainzTrackId')
|
||||
musicBrainzId = None
|
||||
genres = API.getGenres()
|
||||
genre = API.joinList(genres)
|
||||
try:
|
||||
genres = self.genres
|
||||
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))
|
||||
disc = int(item.attrib.get('parentIndex', 1))
|
||||
if disc == 1:
|
||||
|
@ -1613,9 +1613,13 @@ class Music(Items):
|
|||
year = API.getYear()
|
||||
resume, duration = API.getRuntime()
|
||||
rating = userdata['UserRating']
|
||||
|
||||
hasEmbeddedCover = False
|
||||
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 #####
|
||||
doIndirect = not self.directpath
|
||||
|
@ -1653,16 +1657,18 @@ class Music(Items):
|
|||
kodicursor.execute(query, (path, '123', pathid))
|
||||
|
||||
# Update the song entry
|
||||
query = ' '.join((
|
||||
"UPDATE song",
|
||||
"SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?,",
|
||||
"iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?,",
|
||||
"rating = ?, comment = ?",
|
||||
"WHERE idSong = ?"
|
||||
))
|
||||
query = '''
|
||||
UPDATE song
|
||||
SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?,
|
||||
iTrack = ?, iDuration = ?, iYear = ?, strFilename = ?,
|
||||
iTimesPlayed = ?, lastplayed = ?, rating = ?, comment = ?,
|
||||
mood = ?
|
||||
WHERE idSong = ?
|
||||
'''
|
||||
kodicursor.execute(query, (albumid, artists, genre, title, track,
|
||||
duration, year, filename, playcount,
|
||||
dateplayed, rating, comment, songid))
|
||||
dateplayed, rating, comment, mood,
|
||||
songid))
|
||||
|
||||
# Update the checksum in plex table
|
||||
plex_db.updateReference(itemid, checksum)
|
||||
|
@ -1685,7 +1691,9 @@ class Music(Items):
|
|||
if album_name:
|
||||
log.info("Creating virtual music album for song: %s."
|
||||
% 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),
|
||||
v.PLEX_TYPE_ALBUM,
|
||||
albumid,
|
||||
|
@ -1713,54 +1721,51 @@ class Music(Items):
|
|||
except TypeError:
|
||||
# No album found, create a single's album
|
||||
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
|
||||
if v.KODIVERSION >= 16:
|
||||
# Kodi Jarvis
|
||||
query = (
|
||||
'''
|
||||
INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType)
|
||||
|
||||
query = '''
|
||||
INSERT INTO album(
|
||||
idAlbum, strGenres, iYear, strReleaseType)
|
||||
VALUES (?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
kodicursor.execute(query, (albumid, genre, year, "single"))
|
||||
kodicursor.execute(query,
|
||||
(albumid, genre, year, "single"))
|
||||
elif v.KODIVERSION == 15:
|
||||
# Kodi Isengard
|
||||
query = (
|
||||
'''
|
||||
INSERT INTO album(idAlbum, strGenres, iYear, dateAdded, strReleaseType)
|
||||
|
||||
query = '''
|
||||
INSERT INTO album(
|
||||
idAlbum, strGenres, iYear, dateAdded,
|
||||
strReleaseType)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
kodicursor.execute(query, (albumid, genre, year, dateadded, "single"))
|
||||
kodicursor.execute(query, (albumid, genre, year,
|
||||
dateadded, "single"))
|
||||
else:
|
||||
# Kodi Helix
|
||||
query = (
|
||||
'''
|
||||
INSERT INTO album(idAlbum, strGenres, iYear, dateAdded)
|
||||
|
||||
query = '''
|
||||
INSERT INTO album(
|
||||
idAlbum, strGenres, iYear, dateAdded)
|
||||
VALUES (?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
kodicursor.execute(query, (albumid, genre, year, dateadded))
|
||||
kodicursor.execute(query, (albumid, genre, year,
|
||||
dateadded))
|
||||
|
||||
# Create the song entry
|
||||
query = (
|
||||
'''
|
||||
query = '''
|
||||
INSERT INTO song(
|
||||
idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack,
|
||||
iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed,
|
||||
rating, iStartOffset, iEndOffset)
|
||||
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
idSong, idAlbum, idPath, strArtists, strGenres, strTitle,
|
||||
iTrack, iDuration, iYear, strFileName,
|
||||
strMusicBrainzTrackID, iTimesPlayed, lastplayed,
|
||||
rating, iStartOffset, iEndOffset, mood)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
kodicursor.execute(
|
||||
query, (songid, albumid, pathid, artists, genre, title, track,
|
||||
duration, year, filename, musicBrainzId, playcount,
|
||||
dateplayed, rating, 0, 0))
|
||||
dateplayed, rating, 0, 0, mood))
|
||||
|
||||
# Create the reference in plex table
|
||||
plex_db.addReference(itemid,
|
||||
|
@ -1773,14 +1778,11 @@ class Music(Items):
|
|||
view_id=viewid)
|
||||
|
||||
# Link song to album
|
||||
query = (
|
||||
'''
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO albuminfosong(
|
||||
idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration)
|
||||
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
kodicursor.execute(query, (songid, albumid, track, title, duration))
|
||||
|
||||
# Link song to artists
|
||||
|
@ -1808,29 +1810,27 @@ class Music(Items):
|
|||
finally:
|
||||
if v.KODIVERSION >= 17:
|
||||
# Kodi Krypton
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO song_artist(idArtist, idSong, idRole, iOrder, strArtist)
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO song_artist(
|
||||
idArtist, idSong, idRole, iOrder, strArtist)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
kodicursor.execute(query,(artistid, songid, 1, index, artist_name))
|
||||
kodicursor.execute(query, (artistid, songid, 1, index,
|
||||
artist_name))
|
||||
# May want to look into only doing this once?
|
||||
query = (
|
||||
'''
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO role(idRole, strRole)
|
||||
VALUES (?, ?)
|
||||
'''
|
||||
)
|
||||
kodicursor.execute(query, (1, 'Composer'))
|
||||
else:
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO song_artist(idArtist, idSong, iOrder, strArtist)
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO song_artist(
|
||||
idArtist, idSong, iOrder, strArtist)
|
||||
VALUES (?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
kodicursor.execute(query, (artistid, songid, index, artist_name))
|
||||
kodicursor.execute(query, (artistid, songid, index,
|
||||
artist_name))
|
||||
|
||||
# Verify if album artist exists
|
||||
album_artists = []
|
||||
|
@ -1852,31 +1852,28 @@ class Music(Items):
|
|||
artist_edb = plex_db.getItem_byId(artist_eid)
|
||||
artistid = artist_edb[0]
|
||||
finally:
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist)
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO album_artist(
|
||||
idArtist, idAlbum, strArtist)
|
||||
VALUES (?, ?, ?)
|
||||
'''
|
||||
)
|
||||
kodicursor.execute(query, (artistid, albumid, artist_name))
|
||||
# Update discography
|
||||
if item.get('Album'):
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear)
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO discography(
|
||||
idArtist, strAlbum, strYear)
|
||||
VALUES (?, ?, ?)
|
||||
'''
|
||||
)
|
||||
kodicursor.execute(query, (artistid, item['Album'], 0))
|
||||
# else:
|
||||
if False:
|
||||
album_artists = " / ".join(album_artists)
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT strArtists",
|
||||
"FROM album",
|
||||
"WHERE idAlbum = ?"
|
||||
))
|
||||
query = '''
|
||||
SELECT strArtists
|
||||
FROM album
|
||||
WHERE idAlbum = ?
|
||||
'''
|
||||
kodicursor.execute(query, (albumid,))
|
||||
result = kodicursor.fetchone()
|
||||
if result and result[0] != album_artists:
|
||||
|
@ -1895,18 +1892,16 @@ class Music(Items):
|
|||
kodicursor.execute(query, (album_artists, albumid))
|
||||
|
||||
# Add genres
|
||||
self.kodi_db.addMusicGenres(songid, genres, "song")
|
||||
if genres:
|
||||
self.kodi_db.addMusicGenres(songid, genres, v.KODI_TYPE_SONG)
|
||||
|
||||
# Update artwork
|
||||
allart = API.getAllArtwork(parentInfo=True)
|
||||
if hasEmbeddedCover:
|
||||
allart["Primary"] = "image://music@" + artwork.single_urlencode( playurl )
|
||||
artwork.addArtwork(allart, songid, "song", kodicursor)
|
||||
artwork.addArtwork(allart, songid, v.KODI_TYPE_SONG, kodicursor)
|
||||
|
||||
# if item.get('AlbumId') is None:
|
||||
if item.get('parentKey') is None:
|
||||
# Update album artwork
|
||||
artwork.addArtwork(allart, albumid, "album", kodicursor)
|
||||
artwork.addArtwork(allart, albumid, v.KODI_TYPE_ALBUM, kodicursor)
|
||||
|
||||
def remove(self, itemid):
|
||||
# Remove kodiid, fileid, pathid, plex reference
|
||||
|
|
1
resources/lib/library_sync/__init__.py
Normal file
1
resources/lib/library_sync/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# Dummy file to make this directory a package.
|
88
resources/lib/library_sync/fanart.py
Normal file
88
resources/lib/library_sync/fanart.py
Normal 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 ###===---")
|
140
resources/lib/library_sync/get_metadata.py
Normal file
140
resources/lib/library_sync/get_metadata.py
Normal 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')
|
104
resources/lib/library_sync/process_metadata.py
Normal file
104
resources/lib/library_sync/process_metadata.py
Normal 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')
|
81
resources/lib/library_sync/sync_info.py
Normal file
81
resources/lib/library_sync/sync_info.py
Normal 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')
|
|
@ -3,7 +3,7 @@
|
|||
###############################################################################
|
||||
|
||||
import logging
|
||||
from threading import Thread, Lock
|
||||
from threading import Thread
|
||||
import Queue
|
||||
from random import shuffle
|
||||
|
||||
|
@ -28,6 +28,10 @@ import variables as v
|
|||
from PlexFunctions import GetPlexMetadata, GetAllPlexLeaves, scrobble, \
|
||||
GetPlexSectionResults, GetAllPlexChildren, GetPMSStatus
|
||||
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')
|
||||
@ThreadMethodsAdditionalStop('plex_shouldStop')
|
||||
@ThreadMethods
|
||||
|
@ -330,7 +58,7 @@ class LibrarySync(Thread):
|
|||
self.sessionKeys = []
|
||||
self.fanartqueue = Queue.Queue()
|
||||
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?
|
||||
self.saftyMargin = int(settings('backgroundsync_saftyMargin'))
|
||||
|
||||
|
@ -346,7 +74,6 @@ class LibrarySync(Thread):
|
|||
self.enableMusic = settings('enableMusic') == "true"
|
||||
self.enableBackgroundSync = settings(
|
||||
'enableBackgroundSync') == "true"
|
||||
self.limitindex = int(settings('limitindex'))
|
||||
|
||||
# Init for replacing paths
|
||||
window('remapSMB', value=settings('remapSMB'))
|
||||
|
@ -422,8 +149,7 @@ class LibrarySync(Thread):
|
|||
if not view.attrib['type'] == mediatype:
|
||||
continue
|
||||
libraryId = view.attrib['key']
|
||||
items = GetAllPlexLeaves(libraryId,
|
||||
containerSize=self.limitindex)
|
||||
items = GetAllPlexLeaves(libraryId)
|
||||
if items in (None, 401):
|
||||
log.error("Could not download section %s"
|
||||
% view.attrib['key'])
|
||||
|
@ -468,9 +194,7 @@ class LibrarySync(Thread):
|
|||
# Let the PMS process this first!
|
||||
xbmc.sleep(1000)
|
||||
# Get PMS items to find the item we just changed
|
||||
items = GetAllPlexLeaves(libraryId,
|
||||
lastViewedAt=timestamp,
|
||||
containerSize=self.limitindex)
|
||||
items = GetAllPlexLeaves(libraryId, lastViewedAt=timestamp)
|
||||
# Toggle watched state back
|
||||
scrobble(plexId, 'unwatched')
|
||||
if items in (None, 401):
|
||||
|
@ -704,8 +428,8 @@ class LibrarySync(Thread):
|
|||
viewid=folderid,
|
||||
delete=True)
|
||||
# Added new playlist
|
||||
if (foldername not in playlists and
|
||||
mediatype in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
|
||||
if (foldername not in playlists and mediatype in
|
||||
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
|
||||
playlistXSP(mediatype,
|
||||
foldername,
|
||||
folderid,
|
||||
|
@ -730,8 +454,8 @@ class LibrarySync(Thread):
|
|||
else:
|
||||
# Validate the playlist exists or recreate it
|
||||
if mediatype != v.PLEX_TYPE_ARTIST:
|
||||
if (foldername not in playlists and
|
||||
mediatype in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
|
||||
if (foldername not in playlists and mediatype in
|
||||
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
|
||||
playlistXSP(mediatype,
|
||||
foldername,
|
||||
folderid,
|
||||
|
@ -781,7 +505,8 @@ class LibrarySync(Thread):
|
|||
|
||||
for view in sections:
|
||||
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'])
|
||||
log.debug('Sorted views: %s' % self.sorted_views)
|
||||
|
||||
|
@ -863,7 +588,8 @@ class LibrarySync(Thread):
|
|||
with itemtypes.Music() as music:
|
||||
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
|
||||
|
||||
|
@ -876,6 +602,8 @@ class LibrarySync(Thread):
|
|||
see itemtypes.py
|
||||
viewName: Name of the Plex view (e.g. 'My TV shows')
|
||||
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
|
||||
self.updatelist APPENDED(!!) list itemids (Plex Keys as
|
||||
|
@ -910,7 +638,8 @@ class LibrarySync(Thread):
|
|||
'viewName': viewName,
|
||||
'viewId': viewId,
|
||||
'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
|
||||
return
|
||||
|
@ -936,7 +665,8 @@ class LibrarySync(Thread):
|
|||
'viewName': viewName,
|
||||
'viewId': viewId,
|
||||
'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
|
||||
else:
|
||||
|
@ -955,7 +685,8 @@ class LibrarySync(Thread):
|
|||
'viewName': viewName,
|
||||
'viewId': viewId,
|
||||
'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
|
||||
|
||||
|
@ -980,49 +711,38 @@ class LibrarySync(Thread):
|
|||
log.info("Starting sync threads")
|
||||
getMetadataQueue = Queue.Queue()
|
||||
processMetadataQueue = Queue.Queue(maxsize=100)
|
||||
getMetadataLock = Lock()
|
||||
processMetadataLock = Lock()
|
||||
# To keep track
|
||||
global getMetadataCount
|
||||
getMetadataCount = 0
|
||||
global processMetadataCount
|
||||
processMetadataCount = 0
|
||||
global processingViewName
|
||||
processingViewName = ''
|
||||
sync_info.GET_METADATA_COUNT = 0
|
||||
sync_info.PROCESS_METADATA_COUNT = 0
|
||||
sync_info.PROCESSING_VIEW_NAME = ''
|
||||
# Populate queue: GetMetadata
|
||||
for updateItem in self.updatelist:
|
||||
getMetadataQueue.put(updateItem)
|
||||
# Spawn GetMetadata threads for downloading
|
||||
threads = []
|
||||
for i in range(min(self.syncThreadNumber, itemNumber)):
|
||||
thread = ThreadedGetMetadata(getMetadataQueue,
|
||||
processMetadataQueue,
|
||||
getMetadataLock,
|
||||
processMetadataLock)
|
||||
thread = Threaded_Get_Metadata(getMetadataQueue,
|
||||
processMetadataQueue)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
log.info("%s download threads spawned" % len(threads))
|
||||
# Spawn one more thread to process Metadata, once downloaded
|
||||
thread = ThreadedProcessMetadata(processMetadataQueue,
|
||||
itemType,
|
||||
processMetadataLock)
|
||||
thread = Threaded_Process_Metadata(processMetadataQueue,
|
||||
itemType)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
log.info("Processing thread spawned")
|
||||
# Start one thread to show sync progress ONLY for new PMS items
|
||||
if self.new_items_only is True and window('dbSyncIndicator') == 'true':
|
||||
dialog = xbmcgui.DialogProgressBG()
|
||||
thread = ThreadedShowSyncInfo(
|
||||
thread = sync_info.Threaded_Show_Sync_Info(
|
||||
dialog,
|
||||
[getMetadataLock, processMetadataLock],
|
||||
itemNumber,
|
||||
itemType)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
log.info("Kodi Infobox thread spawned")
|
||||
|
||||
# Wait until finished
|
||||
getMetadataQueue.join()
|
||||
|
@ -1083,8 +803,7 @@ class LibrarySync(Thread):
|
|||
# Get items per view
|
||||
viewId = view['id']
|
||||
viewName = view['name']
|
||||
all_plexmovies = GetPlexSectionResults(
|
||||
viewId, args=None, containerSize=self.limitindex)
|
||||
all_plexmovies = GetPlexSectionResults(viewId, args=None)
|
||||
if all_plexmovies is None:
|
||||
log.info("Couldnt get section items, aborting for view.")
|
||||
continue
|
||||
|
@ -1127,8 +846,7 @@ class LibrarySync(Thread):
|
|||
return
|
||||
xml = GetAllPlexLeaves(viewId,
|
||||
lastViewedAt=lastViewedAt,
|
||||
updatedAt=updatedAt,
|
||||
containerSize=self.limitindex)
|
||||
updatedAt=updatedAt)
|
||||
# Return if there are no items in PMS reply - it's faster
|
||||
try:
|
||||
xml[0].attrib
|
||||
|
@ -1178,8 +896,7 @@ class LibrarySync(Thread):
|
|||
# Get items per view
|
||||
viewId = view['id']
|
||||
viewName = view['name']
|
||||
allPlexTvShows = GetPlexSectionResults(
|
||||
viewId, containerSize=self.limitindex)
|
||||
allPlexTvShows = GetPlexSectionResults(viewId)
|
||||
if allPlexTvShows is None:
|
||||
log.error("Error downloading show xml for view %s" % viewId)
|
||||
continue
|
||||
|
@ -1206,8 +923,7 @@ class LibrarySync(Thread):
|
|||
if self.threadStopped():
|
||||
return False
|
||||
# Grab all seasons to tvshow from PMS
|
||||
seasons = GetAllPlexChildren(
|
||||
tvShowId, containerSize=self.limitindex)
|
||||
seasons = GetAllPlexChildren(tvShowId)
|
||||
if seasons is None:
|
||||
log.error("Error download season xml for show %s" % tvShowId)
|
||||
continue
|
||||
|
@ -1232,8 +948,7 @@ class LibrarySync(Thread):
|
|||
if self.threadStopped():
|
||||
return False
|
||||
# Grab all episodes to tvshow from PMS
|
||||
episodes = GetAllPlexLeaves(
|
||||
view['id'], containerSize=self.limitindex)
|
||||
episodes = GetAllPlexLeaves(view['id'])
|
||||
if episodes is None:
|
||||
log.error("Error downloading episod xml for view %s"
|
||||
% view.get('name'))
|
||||
|
@ -1297,12 +1012,17 @@ class LibrarySync(Thread):
|
|||
}
|
||||
|
||||
# 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,
|
||||
v.PLEX_TYPE_ALBUM,
|
||||
v.PLEX_TYPE_SONG):
|
||||
if self.threadStopped():
|
||||
return False
|
||||
log.debug("Start processing music %s" % kind)
|
||||
self.allKodiElementsId = {}
|
||||
self.allPlexElementsId = {}
|
||||
self.updatelist = []
|
||||
if self.ProcessMusic(views,
|
||||
kind,
|
||||
urlArgs[kind],
|
||||
|
@ -1326,10 +1046,8 @@ class LibrarySync(Thread):
|
|||
return True
|
||||
|
||||
def ProcessMusic(self, views, kind, urlArgs, method):
|
||||
self.allKodiElementsId = {}
|
||||
self.allPlexElementsId = {}
|
||||
self.updatelist = []
|
||||
|
||||
# For albums, we need to look at the album's songs simultaneously
|
||||
get_children = True if kind == v.PLEX_TYPE_ALBUM else False
|
||||
# Get a list of items already existing in Kodi db
|
||||
if self.compare:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
|
@ -1340,17 +1058,13 @@ class LibrarySync(Thread):
|
|||
# Yet empty/nothing yet synched
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
for view in views:
|
||||
if self.threadStopped():
|
||||
return False
|
||||
# Get items per view
|
||||
viewId = view['id']
|
||||
viewName = view['name']
|
||||
itemsXML = GetPlexSectionResults(
|
||||
viewId, args=urlArgs, containerSize=self.limitindex)
|
||||
itemsXML = GetPlexSectionResults(view['id'], args=urlArgs)
|
||||
if itemsXML is None:
|
||||
log.error("Error downloading xml for view %s" % viewId)
|
||||
log.error("Error downloading xml for view %s" % view['id'])
|
||||
continue
|
||||
elif itemsXML == 401:
|
||||
return False
|
||||
|
@ -1358,9 +1072,9 @@ class LibrarySync(Thread):
|
|||
self.GetUpdatelist(itemsXML,
|
||||
'Music',
|
||||
method,
|
||||
viewName,
|
||||
viewId)
|
||||
|
||||
view['name'],
|
||||
view['id'],
|
||||
get_children=get_children)
|
||||
if self.compare:
|
||||
# Manual sync, process deletes
|
||||
with itemtypes.Music() as Music:
|
||||
|
|
Loading…
Reference in a new issue