Rewire llibrary sync, part 3
This commit is contained in:
parent
3f4c43e373
commit
e935b7c97b
12 changed files with 564 additions and 155 deletions
|
@ -48,7 +48,7 @@ class ItemBase(object):
|
|||
self.last_sync = last_sync
|
||||
self.artwork = artwork.Artwork()
|
||||
self.plexconn = None
|
||||
self.plexcursor = plex_db.plexcursor if plex_db else None
|
||||
self.plexcursor = plex_db.cursor if plex_db else None
|
||||
self.kodiconn = None
|
||||
self.kodicursor = kodi_db.cursor if kodi_db else None
|
||||
self.plex_db = plex_db
|
||||
|
|
|
@ -11,13 +11,23 @@ LOG = getLogger('PLEX.tvshows')
|
|||
|
||||
|
||||
class TvShowMixin(object):
|
||||
def remove(self, plex_id):
|
||||
def remove(self, plex_id, plex_type=None):
|
||||
"""
|
||||
Remove the entire TV shows object (show, season or episode) including
|
||||
all associated entries from the Kodi DB.
|
||||
"""
|
||||
entry = self.plex_db.getItem_byId(plex_id)
|
||||
if entry is None:
|
||||
if plex_type is None:
|
||||
entry = self.plex_db.episode(plex_id)
|
||||
kodi_type = v.KODI_TYPE_EPISODE
|
||||
if not entry:
|
||||
entry = self.plex_db.season(plex_id)
|
||||
kodi_type = v.KODI_TYPE_SEASON
|
||||
if not entry:
|
||||
entry = self.plex_db.show(plex_id)
|
||||
kodi_type = v.KODI_TYPE_SHOW
|
||||
else:
|
||||
pass
|
||||
if not entry:
|
||||
LOG.debug('Cannot delete plex_id %s - not found in DB', plex_id)
|
||||
return
|
||||
kodi_id = entry[0]
|
||||
|
@ -144,13 +154,12 @@ class Show(ItemBase, TvShowMixin):
|
|||
plex_id = api.plex_id()
|
||||
LOG.debug('Adding show with plex_id %s', plex_id)
|
||||
if not plex_id:
|
||||
LOG.error("Cannot parse XML data for TV show")
|
||||
LOG.error("Cannot parse XML data for TV show: %s", xml.attrib)
|
||||
return
|
||||
update_item = True
|
||||
entry = self.plex_db.getItem_byId(plex_id)
|
||||
show = self.plex_db.show(plex_id)
|
||||
try:
|
||||
kodi_id = entry[0]
|
||||
path_id = entry[2]
|
||||
kodi_id = show[3]
|
||||
kodi_pathid = show[4]
|
||||
except TypeError:
|
||||
update_item = False
|
||||
query = 'SELECT COALESCE(MAX(idShow), 0) FROM tvshow'
|
||||
|
@ -158,8 +167,8 @@ class Show(ItemBase, TvShowMixin):
|
|||
kodi_id = self.kodicursor.fetchone()[0] + 1
|
||||
else:
|
||||
# Verification the item is still in Kodi
|
||||
query = 'SELECT * FROM tvshow WHERE idShow = ?'
|
||||
self.kodicursor.execute(query, (kodi_id,))
|
||||
self.kodicursor.execute('SELECT * FROM tvshow WHERE idShow = ?',
|
||||
(kodi_id,))
|
||||
try:
|
||||
self.kodicursor.fetchone()[0]
|
||||
except TypeError:
|
||||
|
@ -193,7 +202,7 @@ class Show(ItemBase, TvShowMixin):
|
|||
# Do NOT set a parent id because addon-path cannot be "stacked"
|
||||
toppathid = None
|
||||
|
||||
path_id = self.kodi_db.add_video_path(path,
|
||||
kodi_pathid = self.kodi_db.add_video_path(path,
|
||||
date_added=api.date_created(),
|
||||
id_parent_path=toppathid)
|
||||
# UPDATE THE TVSHOW #####
|
||||
|
@ -238,7 +247,7 @@ class Show(ItemBase, TvShowMixin):
|
|||
plex_id, api.title())
|
||||
# Link the path
|
||||
query = "INSERT INTO tvshowlinkpath(idShow, idPath) values (?, ?)"
|
||||
self.kodicursor.execute(query, (kodi_id, path_id))
|
||||
self.kodicursor.execute(query, (kodi_id, kodi_pathid))
|
||||
# Create the reference in plex table
|
||||
|
||||
rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
|
||||
|
@ -284,14 +293,13 @@ class Show(ItemBase, TvShowMixin):
|
|||
tags = [section_name]
|
||||
tags.extend([i for _, i in api.collection_list()])
|
||||
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags)
|
||||
self.plex_db.addReference(plex_id,
|
||||
v.PLEX_TYPE_SHOW,
|
||||
kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
kodi_pathid=path_id,
|
||||
checksum=api.checksum(),
|
||||
view_id=section_id,
|
||||
last_sync=self.last_sync)
|
||||
self.plex_db.add_reference(plex_type=v.PLEX_TYPE_SHOW,
|
||||
plex_id=plex_id,
|
||||
checksum=api.checksum(),
|
||||
section_id=section_id,
|
||||
kodi_id=kodi_id,
|
||||
kodi_pathid=kodi_pathid,
|
||||
last_sync=self.last_sync)
|
||||
|
||||
|
||||
class Season(ItemBase, TvShowMixin):
|
||||
|
@ -304,34 +312,30 @@ class Season(ItemBase, TvShowMixin):
|
|||
plex_id = api.plex_id()
|
||||
LOG.debug('Adding season with plex_id %s', plex_id)
|
||||
if not plex_id:
|
||||
LOG.error('Error getting plex_id for season, skipping')
|
||||
LOG.error('Error getting plex_id for season, skipping: %s',
|
||||
xml.attrib)
|
||||
return
|
||||
entry = self.plex_db.getItem_byId(api.parent_plex_id())
|
||||
show_id = api.parent_plex_id()
|
||||
show = self.plex_db.show(show_id)
|
||||
try:
|
||||
show_id = entry[0]
|
||||
parent_id = show[3]
|
||||
except TypeError:
|
||||
LOG.error('Could not find parent tv show for season %s. '
|
||||
'Skipping season for now.', plex_id)
|
||||
return
|
||||
kodi_id = self.kodi_db.add_season(show_id, api.season_number())
|
||||
# Check whether Season already exists
|
||||
entry = self.plex_db.getItem_byId(plex_id)
|
||||
update_item = False if entry is None else True
|
||||
kodi_id = self.kodi_db.add_season(parent_id, api.season_number())
|
||||
self.artwork.modify_artwork(api.artwork(),
|
||||
kodi_id,
|
||||
v.KODI_TYPE_SEASON,
|
||||
self.kodicursor)
|
||||
if update_item:
|
||||
self.plex_db.updateReference(plex_id, api.checksum())
|
||||
else:
|
||||
self.plex_db.addReference(plex_id,
|
||||
v.PLEX_TYPE_SEASON,
|
||||
kodi_id,
|
||||
v.KODI_TYPE_SEASON,
|
||||
parent_id=show_id,
|
||||
view_id=section_id,
|
||||
checksum=api.checksum(),
|
||||
last_sync=self.last_sync)
|
||||
self.plex_db.add_reference(plex_type=v.PLEX_TYPE_SEASON,
|
||||
plex_id=plex_id,
|
||||
checksum=api.checksum(),
|
||||
section_id=section_id,
|
||||
show_id=show_id,
|
||||
parent_id=parent_id,
|
||||
kodi_id=kodi_id,
|
||||
last_sync=self.last_sync)
|
||||
|
||||
|
||||
class Episode(ItemBase, TvShowMixin):
|
||||
|
@ -345,13 +349,14 @@ class Episode(ItemBase, TvShowMixin):
|
|||
plex_id = api.plex_id()
|
||||
LOG.debug('Adding episode with plex_id %s', plex_id)
|
||||
if not plex_id:
|
||||
LOG.error('Error getting plex_id for episode, skipping')
|
||||
LOG.error('Error getting plex_id for episode, skipping: %s',
|
||||
xml.attrib)
|
||||
return
|
||||
entry = self.plex_db.getItem_byId(plex_id)
|
||||
entry = self.plex_db.item_by_id(plex_id)
|
||||
try:
|
||||
kodi_id = entry[0]
|
||||
old_file_id = entry[1]
|
||||
path_id = entry[2]
|
||||
old_kodi_fileid = entry[1]
|
||||
kodi_pathid = entry[2]
|
||||
except TypeError:
|
||||
update_item = False
|
||||
query = 'SELECT COALESCE(MAX(idEpisode), 0) FROM episode'
|
||||
|
@ -359,7 +364,7 @@ class Episode(ItemBase, TvShowMixin):
|
|||
kodi_id = self.kodicursor.fetchone()[0] + 1
|
||||
else:
|
||||
# Verification the item is still in Kodi
|
||||
query = 'SELECT * FROM episode WHERE idEpisode = ?'
|
||||
query = 'SELECT * FROM episode WHERE idEpisode = ? LIMIT 1'
|
||||
self.kodicursor.execute(query, (kodi_id, ))
|
||||
try:
|
||||
self.kodicursor.fetchone()[0]
|
||||
|
@ -373,23 +378,22 @@ class Episode(ItemBase, TvShowMixin):
|
|||
director = api.list_to_string(peoples['Director'])
|
||||
writer = api.list_to_string(peoples['Writer'])
|
||||
userdata = api.userdata()
|
||||
series_id, _, season, episode = api.episode_data()
|
||||
show_id, season_id, _, season_no, episode_no = api.episode_data()
|
||||
|
||||
if season is None:
|
||||
season = -1
|
||||
if episode is None:
|
||||
episode = -1
|
||||
if season_no is None:
|
||||
season_no = -1
|
||||
if episode_no is None:
|
||||
episode_no = -1
|
||||
airs_before_season = "-1"
|
||||
airs_before_episode = "-1"
|
||||
|
||||
# Get season id
|
||||
show = self.plex_db.getItem_byId(series_id)
|
||||
show = self.plex_db.show(show_id)
|
||||
try:
|
||||
show_id = show[0]
|
||||
grandparent_id = show[3]
|
||||
except TypeError:
|
||||
LOG.error("Parent tvshow now found, skip item")
|
||||
return False
|
||||
season_id = self.kodi_db.add_season(show_id, season)
|
||||
parent_id = self.kodi_db.add_season(grandparent_id, season_no)
|
||||
|
||||
# GET THE FILE AND PATH #####
|
||||
do_indirect = not state.DIRECT_PATHS
|
||||
|
@ -407,29 +411,31 @@ class Episode(ItemBase, TvShowMixin):
|
|||
filename = playurl.rsplit("/", 1)[1]
|
||||
path = playurl.replace(filename, "")
|
||||
parent_path_id = self.kodi_db.parent_path_id(path)
|
||||
path_id = self.kodi_db.add_video_path(
|
||||
kodi_pathid = self.kodi_db.add_video_path(
|
||||
path, id_parent_path=parent_path_id)
|
||||
if do_indirect:
|
||||
# Set plugin path - do NOT use "intermediate" paths for the show
|
||||
# as with direct paths!
|
||||
filename = api.file_name(force_first_media=True)
|
||||
path = 'plugin://%s.tvshows/%s/' % (v.ADDON_ID, series_id)
|
||||
path = 'plugin://%s.tvshows/%s/' % (v.ADDON_ID, show_id)
|
||||
filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s'
|
||||
% (path, plex_id, v.PLEX_TYPE_EPISODE, filename))
|
||||
playurl = filename
|
||||
# Root path tvshows/ already saved in Kodi DB
|
||||
path_id = self.kodi_db.add_video_path(path)
|
||||
kodi_pathid = self.kodi_db.add_video_path(path)
|
||||
|
||||
# add/retrieve path_id and fileid
|
||||
# add/retrieve kodi_pathid and fileid
|
||||
# if the path or file already exists, the calls return current value
|
||||
file_id = self.kodi_db.add_file(filename, path_id, api.date_created())
|
||||
kodi_fileid = self.kodi_db.add_file(filename,
|
||||
kodi_pathid,
|
||||
api.date_created())
|
||||
|
||||
# UPDATE THE EPISODE #####
|
||||
if update_item:
|
||||
LOG.info("UPDATE episode plex_id: %s, Title: %s",
|
||||
plex_id, api.title())
|
||||
if file_id != old_file_id:
|
||||
self.kodi_db.remove_file(old_file_id)
|
||||
if kodi_fileid != old_kodi_fileid:
|
||||
self.kodi_db.remove_file(old_kodi_fileid)
|
||||
ratingid = self.kodi_db.get_ratingid(kodi_id,
|
||||
v.KODI_TYPE_EPISODE)
|
||||
self.kodi_db.update_ratings(kodi_id,
|
||||
|
@ -456,12 +462,10 @@ class Episode(ItemBase, TvShowMixin):
|
|||
'''
|
||||
self.kodicursor.execute(
|
||||
query, (api.title(), api.plot(), ratingid, writer,
|
||||
api.premiere_date(), api.runtime(), director, season,
|
||||
episode, api.title(), airs_before_season,
|
||||
airs_before_episode, playurl, path_id, file_id,
|
||||
season_id, userdata['UserRating'], kodi_id))
|
||||
# Update parentid reference
|
||||
self.plex_db.updateParentId(plex_id, season_id)
|
||||
api.premiere_date(), api.runtime(), director, season_no,
|
||||
episode_no, api.title(), airs_before_season,
|
||||
airs_before_episode, playurl, kodi_pathid, kodi_fileid,
|
||||
parent_id, userdata['UserRating'], kodi_id))
|
||||
|
||||
# OR ADD THE EPISODE #####
|
||||
else:
|
||||
|
@ -492,11 +496,11 @@ class Episode(ItemBase, TvShowMixin):
|
|||
?, ?)
|
||||
'''
|
||||
self.kodicursor.execute(
|
||||
query, (kodi_id, file_id, api.title(), api.plot(), rating_id,
|
||||
query, (kodi_id, kodi_fileid, api.title(), api.plot(), rating_id,
|
||||
writer, api.premiere_date(), api.runtime(), director,
|
||||
season, episode, api.title(), show_id,
|
||||
season_no, episode_no, api.title(), grandparent_id,
|
||||
airs_before_season, airs_before_episode, playurl,
|
||||
path_id, season_id, userdata['UserRating']))
|
||||
kodi_pathid, parent_id, userdata['UserRating']))
|
||||
|
||||
self.kodi_db.modify_people(kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
|
@ -506,8 +510,8 @@ class Episode(ItemBase, TvShowMixin):
|
|||
v.KODI_TYPE_EPISODE,
|
||||
self.kodicursor)
|
||||
streams = api.mediastreams()
|
||||
self.kodi_db.modify_streams(file_id, streams, api.runtime())
|
||||
self.kodi_db.set_resume(file_id,
|
||||
self.kodi_db.modify_streams(kodi_fileid, streams, api.runtime())
|
||||
self.kodi_db.set_resume(kodi_fileid,
|
||||
api.resume_point(),
|
||||
api.runtime(),
|
||||
userdata['PlayCount'],
|
||||
|
@ -519,25 +523,27 @@ class Episode(ItemBase, TvShowMixin):
|
|||
path = 'plugin://%s.tvshows/' % v.ADDON_ID
|
||||
# Filename is exactly the same, WITH plex show id!
|
||||
filename = ('%s%s/?plex_id=%s&plex_type=%s&mode=play&filename=%s'
|
||||
% (path, series_id, plex_id, v.PLEX_TYPE_EPISODE,
|
||||
% (path, show_id, plex_id, v.PLEX_TYPE_EPISODE,
|
||||
filename))
|
||||
path_id = self.kodi_db.add_video_path(path)
|
||||
file_id = self.kodi_db.add_file(filename,
|
||||
path_id,
|
||||
api.date_created())
|
||||
self.kodi_db.set_resume(file_id,
|
||||
kodi_pathid = self.kodi_db.add_video_path(path)
|
||||
kodi_fileid = self.kodi_db.add_file(filename,
|
||||
kodi_pathid,
|
||||
api.date_created())
|
||||
self.kodi_db.set_resume(kodi_fileid,
|
||||
api.resume_point(),
|
||||
api.runtime(),
|
||||
userdata['PlayCount'],
|
||||
userdata['LastPlayedDate'],
|
||||
None) # Do send None - 2nd entry
|
||||
self.plex_db.addReference(plex_id,
|
||||
v.PLEX_TYPE_EPISODE,
|
||||
kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
kodi_file_id=file_id,
|
||||
kodi_pathid=path_id,
|
||||
parent_id=season_id,
|
||||
checksum=api.checksum(),
|
||||
view_id=section_id,
|
||||
last_sync=self.last_sync)
|
||||
self.plex_db.add_reference(plex_type=v.PLEX_TYPE_EPISODE,
|
||||
plex_id=plex_id,
|
||||
checksum=api.checksum(),
|
||||
section_id=section_id,
|
||||
show_id=show_id,
|
||||
grandparent_id=grandparent_id,
|
||||
season_id=season_id,
|
||||
parent_id=parent_id,
|
||||
kodi_id=kodi_id,
|
||||
kodi_fileid=kodi_fileid,
|
||||
kodi_pathid=kodi_pathid,
|
||||
last_sync=self.last_sync)
|
||||
|
|
|
@ -30,9 +30,10 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
|||
self.process_thread = None
|
||||
self.last_sync = None
|
||||
self.plex_db = None
|
||||
self.plex_type = None
|
||||
super(FullSync, self).__init__()
|
||||
|
||||
def process_item(self, xml_item, get_children):
|
||||
def process_item(self, xml_item):
|
||||
"""
|
||||
Processes a single library item
|
||||
"""
|
||||
|
@ -42,7 +43,7 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
|||
backgroundthread.BGThreader.addTask(
|
||||
GetMetadataTask().setup(self.queue,
|
||||
plex_id,
|
||||
get_children))
|
||||
self.get_children))
|
||||
else:
|
||||
if self.plex_db.check_checksum(
|
||||
int('%s%s' % (xml_item['ratingKey'],
|
||||
|
@ -50,20 +51,29 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
|||
backgroundthread.BGThreader.addTask(
|
||||
GetMetadataTask().setup(self.queue,
|
||||
plex_id,
|
||||
get_children))
|
||||
self.get_children))
|
||||
else:
|
||||
self.plex_db.update_last_sync(plex_id, self.last_sync)
|
||||
|
||||
def process_delete(self):
|
||||
"""
|
||||
Removes all the items that have NOT been updated (last_sync timestamp)
|
||||
is different
|
||||
"""
|
||||
with self.context() as c:
|
||||
for plex_id in self.plex_db.plex_id_by_last_sync(self.plex_type,
|
||||
self.last_sync):
|
||||
if self.isCanceled():
|
||||
return
|
||||
c.remove(plex_id, plex_type=self.plex_type)
|
||||
|
||||
@utils.log_time
|
||||
def process_kind(self, kind):
|
||||
def process_kind(self):
|
||||
"""
|
||||
kind is a tuple: (<name as unicode>,
|
||||
kodi_type,
|
||||
<itemtype class>,
|
||||
get_children)
|
||||
"""
|
||||
LOG.debug('Start processing %s', kind[0])
|
||||
sections = (x for x in sections.SECTIONS if x['kodi_type'] == kind[1])
|
||||
LOG.debug('Start processing %ss', self.plex_type)
|
||||
sections = (x for x in sections.SECTIONS
|
||||
if x['plex_type'] == self.plex_type)
|
||||
for section in sections:
|
||||
LOG.debug('Processing library section %s', section)
|
||||
if self.isCanceled():
|
||||
|
@ -71,10 +81,12 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
|||
if not self.install_sync_done:
|
||||
state.PATH_VERIFIED = False
|
||||
try:
|
||||
iterator = PF.PlexSectionItems(section['id'])
|
||||
iterator = PF.SectionItems(
|
||||
section['id'],
|
||||
{'type': v.PLEX_TYPE_NUMBER_FROM_PLEX_TYPE[self.plex_type]})
|
||||
# Tell the processing thread about this new section
|
||||
queue_info = process_metadata.InitNewSection(
|
||||
kind[2],
|
||||
self.context,
|
||||
utils.cast(int, iterator.get('totalSize', 0)),
|
||||
utils.cast(unicode, iterator.get('librarySectionTitle')),
|
||||
section['id'])
|
||||
|
@ -82,32 +94,42 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
|||
for xml_item in iterator:
|
||||
if self.isCanceled():
|
||||
return False
|
||||
self.process_item(xml_item, kind[3])
|
||||
self.process_item(xml_item)
|
||||
except RuntimeError:
|
||||
LOG.error('Could not entirely process section %s', section)
|
||||
continue
|
||||
LOG.debug('Finished processing %s', kind[0])
|
||||
|
||||
LOG.debug('Finished processing %ss', self.plex_type)
|
||||
return True
|
||||
|
||||
def full_library_sync(self, new_items_only=False):
|
||||
def full_library_sync(self):
|
||||
"""
|
||||
"""
|
||||
process = [self.plex_movies, self.plex_tv_show]
|
||||
if state.ENABLE_MUSIC:
|
||||
process.append(self.plex_music)
|
||||
self.queue = backgroundthread.Queue.Queue(maxsize=200)
|
||||
t = process_metadata.ProcessMetadata(self.queue, self.last_sync)
|
||||
t.start()
|
||||
|
||||
kinds = [
|
||||
('movies', v.KODI_TYPE_MOVIE, itemtypes.Movie, False),
|
||||
('tv shows', v.KODI_TYPE_SHOW, itemtypes.Show, False),
|
||||
('tv seasons', v.KODI_TYPE_SEASON, itemtypes.Season, False),
|
||||
('tv shows', v.KODI_TYPE_SHOW, itemtypes.Show, False),
|
||||
(v.PLEX_TYPE_MOVIE, itemtypes.Movie, False),
|
||||
(v.PLEX_TYPE_SHOW, itemtypes.Show, False),
|
||||
(v.PLEX_TYPE_SEASON, itemtypes.Season, False),
|
||||
(v.PLEX_TYPE_EPISODE, itemtypes.Episode, False),
|
||||
(v.PLEX_TYPE_ARTIST, itemtypes.Artist, False),
|
||||
(v.PLEX_TYPE_ALBUM, itemtypes.Album, True),
|
||||
(v.PLEX_TYPE_SONG, itemtypes.Song, False),
|
||||
]
|
||||
try:
|
||||
for kind in kinds:
|
||||
if self.isCanceled() or not self.process_kind(kind):
|
||||
return False
|
||||
for kind in kinds:
|
||||
# Setup our variables
|
||||
self.plex_type = kind[0]
|
||||
self.context = kind[1]
|
||||
self.get_children = kind[2]
|
||||
# Now do the heavy lifting
|
||||
if self.isCanceled() or not self.process_kind():
|
||||
return False
|
||||
if not self.new_items_only:
|
||||
# Delete movies that are not on Plex anymore - do this only once
|
||||
self.process_delete()
|
||||
|
||||
|
||||
# Let kodi update the views in any case, since we're doing a full sync
|
||||
common.update_kodi_library(video=True, music=state.ENABLE_MUSIC)
|
||||
|
|
|
@ -54,10 +54,10 @@ def sync_from_pms():
|
|||
|
||||
VNODES.clearProperties()
|
||||
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
with plexdb.PlexDB() as plex_db:
|
||||
# Backup old sections to delete them later, if needed (at the end
|
||||
# of this method, only unused sections will be left in old_sections)
|
||||
old_sections = plex_db.sections()
|
||||
old_sections = [plex_db.section_ids()]
|
||||
with kodidb.GetKodiDB('video') as kodi_db:
|
||||
for section in sections:
|
||||
_process_section(section,
|
||||
|
@ -71,8 +71,8 @@ def sync_from_pms():
|
|||
# Section has been deleted on the PMS
|
||||
delete_sections(old_sections)
|
||||
# update sections for all:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
SECTIONS = plex_db.list_section_info()
|
||||
with plexdb.PlexDB() as plex_db:
|
||||
SECTIONS = [plex_db.section_infos()]
|
||||
utils.window('Plex.nodes.total', str(totalnodes))
|
||||
LOG.info("Finished processing library sections: %s", SECTIONS)
|
||||
return True
|
||||
|
@ -209,7 +209,7 @@ def delete_sections(old_sections):
|
|||
sound=False)
|
||||
video_library_update = False
|
||||
music_library_update = False
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
with plexdb.PlexDB() as plex_db:
|
||||
old_sections = [plex_db.section_by_id(x) for x in old_sections]
|
||||
LOG.info("Removing entire Plex library sections: %s", old_sections)
|
||||
with kodidb.GetKodiDB() as kodi_db:
|
||||
|
|
|
@ -214,20 +214,6 @@ class LibrarySync(Thread):
|
|||
"""
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
# Create the tables for the plex database
|
||||
plex_db.plexcursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS plex(
|
||||
plex_id INTEGER PRIMARY KEY ASC,
|
||||
section_id INTEGER,
|
||||
plex_type TEXT,
|
||||
kodi_type TEXT,
|
||||
kodi_id INTEGER,
|
||||
kodi_fileid INTEGER,
|
||||
kodi_pathid INTEGER,
|
||||
parent_id INTEGER,
|
||||
checksum INTEGER UNIQUE,
|
||||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.plexcursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS sections(
|
||||
section_id INTEGER PRIMARY KEY,
|
||||
|
@ -236,6 +222,88 @@ class LibrarySync(Thread):
|
|||
kodi_tagid INTEGER,
|
||||
sync_to_kodi INTEGER)
|
||||
''')
|
||||
plex_db.plexcursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS movie(
|
||||
plex_id INTEGER PRIMARY KEY ASC,
|
||||
checksum INTEGER UNIQUE,
|
||||
section_id INTEGER,
|
||||
kodi_id INTEGER,
|
||||
kodi_fileid INTEGER,
|
||||
kodi_pathid INTEGER,
|
||||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.plexcursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS show(
|
||||
plex_id INTEGER PRIMARY KEY ASC,
|
||||
checksum INTEGER UNIQUE,
|
||||
section_id INTEGER,
|
||||
kodi_id INTEGER,
|
||||
kodi_pathid INTEGER,
|
||||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.plexcursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS season(
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
checksum INTEGER UNIQUE,
|
||||
section_id INTEGER,
|
||||
show_id INTEGER, # plex_id of the parent show
|
||||
parent_id INTEGER, # kodi_id of the parent show
|
||||
kodi_id INTEGER,
|
||||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.plexcursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS episode(
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
checksum INTEGER UNIQUE,
|
||||
section_id INTEGER,
|
||||
show_id INTEGER, # plex_id of the parent show
|
||||
grandparent_id INTEGER, # kodi_id of the parent show
|
||||
season_id INTEGER, # plex_id of the parent season
|
||||
parent_id INTEGER, # kodi_id of the parent season
|
||||
kodi_id INTEGER,
|
||||
kodi_fileid INTEGER,
|
||||
kodi_pathid INTEGER,
|
||||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.plexcursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS artist(
|
||||
plex_id INTEGER PRIMARY KEY ASC,
|
||||
checksum INTEGER UNIQUE,
|
||||
section_id INTEGER,
|
||||
kodi_id INTEGER,
|
||||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.plexcursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS album(
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
checksum INTEGER UNIQUE,
|
||||
section_id INTEGER,
|
||||
artist_id INTEGER, # plex_id of the parent artist
|
||||
parent_id INTEGER, # kodi_id of the parent artist
|
||||
kodi_id INTEGER,
|
||||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.plexcursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS track(
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
checksum INTEGER UNIQUE,
|
||||
section_id INTEGER,
|
||||
artist_id INTEGER, # plex_id of the parent artist
|
||||
grandparent_id INTEGER, # kodi_id of the parent artist
|
||||
album_id INTEGER, # plex_id of the parent album
|
||||
parent_id INTEGER, # kodi_id of the parent album
|
||||
kodi_id INTEGER,
|
||||
kodi_fileid INTEGER,
|
||||
kodi_pathid INTEGER,
|
||||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.plexcursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS playlists(
|
||||
plex_id INTEGER PRIMARY KEY ASC,
|
||||
|
|
|
@ -95,17 +95,17 @@ def _write_playlist_to_file(playlist, xml):
|
|||
api = API(element)
|
||||
append_season_episode = False
|
||||
if api.plex_type() == v.PLEX_TYPE_EPISODE:
|
||||
_, show, season_id, episode_id = api.episode_data()
|
||||
_, _, show, season_no, episode_no = api.episode_data()
|
||||
try:
|
||||
season_id = int(season_id)
|
||||
episode_id = int(episode_id)
|
||||
season_no = int(season_no)
|
||||
episode_no = int(episode_no)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
append_season_episode = True
|
||||
if append_season_episode:
|
||||
text += ('#EXTINF:%s,%s S%.2dE%.2d - %s\n%s\n'
|
||||
% (api.runtime(), show, season_id, episode_id,
|
||||
% (api.runtime(), show, season_no, episode_no,
|
||||
api.title(), api.path()))
|
||||
else:
|
||||
# Only append the TV show name
|
||||
|
|
|
@ -260,9 +260,9 @@ class API(object):
|
|||
|
||||
def season_number(self):
|
||||
"""
|
||||
Returns the 'index' of an PMS XML reply. Depicts e.g. season number.
|
||||
Returns the 'index' of an XML reply as int. Depicts e.g. season number.
|
||||
"""
|
||||
return self.item.get('index')
|
||||
return cast(int, self.item.get('index'))
|
||||
|
||||
def date_created(self):
|
||||
"""
|
||||
|
@ -639,14 +639,14 @@ class API(object):
|
|||
"""
|
||||
Returns the 'parentRatingKey' as a string or None
|
||||
"""
|
||||
return self.item.get('parentRatingKey')
|
||||
return cast(int, self.item.get('parentRatingKey'))
|
||||
|
||||
def grandparent_id(self):
|
||||
"""
|
||||
Returns the ratingKey for the corresponding grandparent, e.g. a TV show
|
||||
for episodes, or None
|
||||
"""
|
||||
return self.item.get('grandparentRatingKey')
|
||||
return cast(int, self.item.get('grandparentRatingKey'))
|
||||
|
||||
def grandparent_title(self):
|
||||
"""
|
||||
|
@ -661,16 +661,18 @@ class API(object):
|
|||
|
||||
Output: for the corresponding the TV show and season:
|
||||
[
|
||||
TV show key, Plex: 'grandparentRatingKey'
|
||||
TV show ID, Plex: 'grandparentRatingKey'
|
||||
TV season ID, Plex: 'grandparentRatingKey'
|
||||
TV show title, Plex: 'grandparentTitle'
|
||||
TV show season, Plex: 'parentIndex'
|
||||
Episode number, Plex: 'index'
|
||||
]
|
||||
"""
|
||||
return (self.item.get('grandparentRatingKey'),
|
||||
self.item.get('grandparentTitle'),
|
||||
self.item.get('parentIndex'),
|
||||
self.item.get('index'))
|
||||
return (cast(int, self.item.get('grandparentRatingKey')),
|
||||
cast(int, self.item.get('parentRatingKey')),
|
||||
cast(unicode, self.item.get('grandparentTitle')),
|
||||
cast(int, self.item.get('parentIndex')),
|
||||
cast(int, self.item.get('index')))
|
||||
|
||||
@staticmethod
|
||||
def attach_plex_token_to_url(url):
|
||||
|
@ -1609,7 +1611,7 @@ class API(object):
|
|||
|
||||
if typus == v.PLEX_TYPE_EPISODE:
|
||||
metadata['mediatype'] = 'episode'
|
||||
_, show, season, episode = self.episode_data()
|
||||
_, _, show, season, episode = self.episode_data()
|
||||
season = -1 if season is None else int(season)
|
||||
episode = -1 if episode is None else int(episode)
|
||||
metadata['episode'] = episode
|
||||
|
|
43
resources/lib/plex_db/__init__.py
Normal file
43
resources/lib/plex_db/__init__.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
|
||||
from .common import PlexDBBase
|
||||
from .tvshows import
|
||||
from .. import utils, variables as v
|
||||
|
||||
|
||||
class PlexDB(object):
|
||||
"""
|
||||
Usage: with PlexDB() as plex_db:
|
||||
plex_db.do_something()
|
||||
|
||||
On exiting "with" (no matter what), commits get automatically committed
|
||||
and the db gets closed
|
||||
"""
|
||||
def __init__(self, kind=None):
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
self.plexconn = utils.kodi_sql('plex')
|
||||
if kind is None:
|
||||
func = PlexDBBase
|
||||
return func(self.plexconn.cursor())
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.plexconn.commit()
|
||||
self.plexconn.close()
|
||||
|
||||
|
||||
def wipe_dbs():
|
||||
"""
|
||||
Completely resets the Plex database
|
||||
"""
|
||||
query = "SELECT name FROM sqlite_master WHERE type = 'table'"
|
||||
with PlexDB() as plex_db:
|
||||
plex_db.plexcursor.execute(query)
|
||||
tables = plex_db.plexcursor.fetchall()
|
||||
tables = [i[0] for i in tables]
|
||||
for table in tables:
|
||||
delete_query = 'DELETE FROM %s' % table
|
||||
plex_db.plexcursor.execute(delete_query)
|
112
resources/lib/plex_db/common.py
Normal file
112
resources/lib/plex_db/common.py
Normal file
|
@ -0,0 +1,112 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
|
||||
|
||||
class PlexDB(object):
|
||||
"""
|
||||
Methods used for all types of items
|
||||
"""
|
||||
def __init__(self, cursor):
|
||||
self.cursor = cursor
|
||||
|
||||
def section_ids(self):
|
||||
"""
|
||||
Returns an iterator for section Plex ids for all sections
|
||||
"""
|
||||
self.cursor.execute('SELECT section_id FROM sections')
|
||||
return (x[0] for x in self.cursor)
|
||||
|
||||
def section_infos(self):
|
||||
"""
|
||||
Returns an iterator for dicts for all Plex libraries:
|
||||
{
|
||||
'section_id'
|
||||
'section_name'
|
||||
'plex_type'
|
||||
'kodi_tagid'
|
||||
'sync_to_kodi'
|
||||
}
|
||||
"""
|
||||
self.cursor.execute('SELECT * FROM sections')
|
||||
return ({'section_id': x[0],
|
||||
'section_name': x[1],
|
||||
'plex_type': x[2],
|
||||
'kodi_tagid': x[3],
|
||||
'sync_to_kodi': x[4]} for x in self.cursor)
|
||||
|
||||
def section_by_id(self, section_id):
|
||||
"""
|
||||
For section_id, returns tuple (or None)
|
||||
(section_id,
|
||||
section_name,
|
||||
plex_type,
|
||||
kodi_tagid,
|
||||
sync_to_kodi)
|
||||
"""
|
||||
self.cursor.execute('SELECT * FROM sections WHERE section_id = ? LIMIT 1',
|
||||
(section_id, ))
|
||||
return self.cursor.fetchone()
|
||||
|
||||
def section_id_by_name(self, section_name):
|
||||
"""
|
||||
Returns the section_id for section_name (or None)
|
||||
"""
|
||||
self.cursor.execute('SELECT section_id FROM sections WHERE section_name = ? LIMIT 1,'
|
||||
(section_name, ))
|
||||
try:
|
||||
return self.cursor.fetchone()[0]
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
def add_section(self, section_id, section_name, plex_type, kodi_tagid,
|
||||
sync_to_kodi=True):
|
||||
"""
|
||||
Appends a Plex section to the Plex sections table
|
||||
sync=False: Plex library won't be synced to Kodi
|
||||
"""
|
||||
query = '''
|
||||
INSERT INTO sections(
|
||||
section_id, section_name, plex_type, kodi_tagid, sync_to_kodi)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
'''
|
||||
self.cursor.execute(query,
|
||||
(section_id,
|
||||
section_name,
|
||||
plex_type,
|
||||
kodi_tagid,
|
||||
sync_to_kodi))
|
||||
|
||||
def update_section(self, section_name, kodi_tagid, section_id):
|
||||
"""
|
||||
Updates the section_id with section_name and kodi_tagid
|
||||
"""
|
||||
query = '''
|
||||
UPDATE sections
|
||||
SET section_name = ?, kodi_tagid = ?
|
||||
WHERE section_id = ?
|
||||
'''
|
||||
self.cursor.execute(query, (section_name, kodi_tagid, section_id))
|
||||
|
||||
def remove_section(self, section_id):
|
||||
"""
|
||||
Removes the Plex db entry for the section with section_id
|
||||
"""
|
||||
self.cursor.execute('DELETE FROM sections WHERE section_id = ?',
|
||||
(section_id, ))
|
||||
|
||||
def item_by_id(self, plex_id):
|
||||
"""
|
||||
For plex_id, returns the tuple
|
||||
(kodi_id, kodi_fileid, kodi_pathid, parent_id, kodi_type, plex_type)
|
||||
|
||||
None if not found
|
||||
"""
|
||||
query = '''
|
||||
SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, kodi_type,
|
||||
plex_type
|
||||
FROM plex WHERE plex_id = ?
|
||||
LIMIT 1
|
||||
'''
|
||||
self.cursor.execute(query, (plex_id,))
|
||||
return self.cursor.fetchone()
|
124
resources/lib/plex_db/tvshows.py
Normal file
124
resources/lib/plex_db/tvshows.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
|
||||
from . import common
|
||||
from .. import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
class PlexDB(common.PlexDB):
|
||||
def add_reference(self, plex_type=None, plex_id=None, checksum=None,
|
||||
section_id=None, show_id=None, grandparent_id=None,
|
||||
season_id=None, parent_id=None, kodi_id=None,
|
||||
kodi_fileid=None, kodi_pathid=None, last_sync=None):
|
||||
"""
|
||||
Appends or replaces an entry into the plex table
|
||||
"""
|
||||
if plex_type == v.PLEX_TYPE_EPISODE:
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO episode(
|
||||
plex_id, checksum, section_id, show_id, grandparent_id,
|
||||
season_id, parent_id, kodi_id, kodi_fileid, kodi_pathid,
|
||||
fanart_synced, last_sync)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
self.plexcursor.execute(
|
||||
query,
|
||||
(plex_id, checksum, section_id, show_id, grandparent_id,
|
||||
season_id, parent_id, kodi_id, kodi_fileid, kodi_pathid,
|
||||
0, last_sync))
|
||||
elif plex_type == v.PLEX_TYPE_SEASON:
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO season(
|
||||
plex_id, checksum, section_id, show_id, parent_id,
|
||||
kodi_id, fanart_synced, last_sync)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
self.plexcursor.execute(
|
||||
query,
|
||||
(plex_id, checksum, section_id, show_id, parent_id,
|
||||
kodi_id, 0, last_sync))
|
||||
elif plex_type == v.PLEX_TYPE_SHOW:
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO show(
|
||||
plex_id, checksum, section_id, kodi_id, kodi_pathid,
|
||||
fanart_synced, last_sync)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
self.plexcursor.execute(
|
||||
query,
|
||||
(plex_id, checksum, section_id, kodi_id, kodi_pathid, 0,
|
||||
last_sync))
|
||||
|
||||
def show(self, plex_id):
|
||||
"""
|
||||
Returns the show info as a tuple for the TV show with plex_id:
|
||||
plex_id INTEGER PRIMARY KEY ASC,
|
||||
checksum INTEGER UNIQUE,
|
||||
section_id INTEGER,
|
||||
kodi_id INTEGER,
|
||||
kodi_pathid INTEGER,
|
||||
fanart_synced INTEGER,
|
||||
last_sync INTEGER
|
||||
"""
|
||||
self.cursor.execute('SELECT * FROM show WHERE plex_id = ?',
|
||||
(plex_id, ))
|
||||
return self.cursor.fetchone()
|
||||
|
||||
def season(self, plex_id):
|
||||
"""
|
||||
Returns the show info as a tuple for the TV show with plex_id:
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
checksum INTEGER UNIQUE,
|
||||
section_id INTEGER,
|
||||
show_id INTEGER, # plex_id of the parent show
|
||||
parent_id INTEGER, # kodi_id of the parent show
|
||||
kodi_id INTEGER,
|
||||
fanart_synced INTEGER,
|
||||
last_sync INTEGER
|
||||
"""
|
||||
self.cursor.execute('SELECT * FROM season WHERE plex_id = ?',
|
||||
(plex_id, ))
|
||||
return self.cursor.fetchone()
|
||||
|
||||
def episode(self, plex_id):
|
||||
"""
|
||||
Returns the show info as a tuple for the TV show with plex_id:
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
checksum INTEGER UNIQUE,
|
||||
section_id INTEGER,
|
||||
show_id INTEGER, # plex_id of the parent show
|
||||
grandparent_id INTEGER, # kodi_id of the parent show
|
||||
season_id INTEGER, # plex_id of the parent season
|
||||
parent_id INTEGER, # kodi_id of the parent season
|
||||
kodi_id INTEGER,
|
||||
kodi_fileid INTEGER,
|
||||
kodi_pathid INTEGER,
|
||||
fanart_synced INTEGER,
|
||||
last_sync INTEGER
|
||||
"""
|
||||
self.cursor.execute('SELECT * FROM episode WHERE plex_id = ?',
|
||||
(plex_id, ))
|
||||
return self.cursor.fetchone()
|
||||
|
||||
def plex_id_by_last_sync(self, plex_type, last_sync):
|
||||
"""
|
||||
Returns an iterator for all items where the last_sync is NOT identical
|
||||
"""
|
||||
self.cursor.execute('SELECT plex_id FROM ? WHERE last_sync <> ?',
|
||||
(plex_type, last_sync, ))
|
||||
return (x[0] for x in self.cursor)
|
||||
|
||||
def shows_plex_id_section_id(self):
|
||||
"""
|
||||
Iterator for tuples (plex_id, section_id) of all our TV shows
|
||||
"""
|
||||
self.cursor.execute('SELECT plex_id, section_id FROM show')
|
||||
return self.cursor
|
||||
|
||||
def update_last_sync(self, plex_type, plex_id, last_sync):
|
||||
"""
|
||||
Sets a new timestamp for plex_id
|
||||
"""
|
|
@ -534,7 +534,8 @@ class DownloadGen(object):
|
|||
|
||||
Yields XML etree children or raises RuntimeError
|
||||
"""
|
||||
def __init__(self, url):
|
||||
def __init__(self, url, args=None):
|
||||
self._args = args or {}
|
||||
self._url = url
|
||||
self._pos = 0
|
||||
self._exhausted = False
|
||||
|
@ -542,17 +543,18 @@ class DownloadGen(object):
|
|||
self.attrib = deepcopy(self.xml.attrib)
|
||||
|
||||
def _download_chunk(self):
|
||||
args = {
|
||||
self._args.update({
|
||||
'X-Plex-Container-Size': CONTAINERSIZE,
|
||||
'X-Plex-Container-Start': self._pos,
|
||||
'sort': 'id'
|
||||
}
|
||||
self.xml = DU().downloadUrl(self._url, parameters=args)
|
||||
'sort': 'id', # Entries are sorted by plex_id
|
||||
'excludeAllLeaves': 1 # PMS wont attach a first summary child
|
||||
})
|
||||
self.xml = DU().downloadUrl(self._url, parameters=self._args)
|
||||
try:
|
||||
self.xml.attrib
|
||||
except AttributeError:
|
||||
LOG.error('Error while downloading chunks: %s, args: %s',
|
||||
self._url, args)
|
||||
self._url, self._args)
|
||||
raise RuntimeError('Error while downloading chunks for %s'
|
||||
% self._url)
|
||||
|
||||
|
@ -582,13 +584,31 @@ class DownloadGen(object):
|
|||
return self.attrib.get(key, default)
|
||||
|
||||
|
||||
class PlexSectionItems(DownloadGen):
|
||||
class SectionItems(DownloadGen):
|
||||
"""
|
||||
Iterator object to get all items of a Plex library section
|
||||
"""
|
||||
def __init__(self, section_id, args):
|
||||
super(SectionItems, self).__init__(
|
||||
'{server}/library/sections/%s/all' % section_id, args)
|
||||
|
||||
|
||||
class Children(DownloadGen):
|
||||
"""
|
||||
Iterator object to get all items of a Plex library section
|
||||
"""
|
||||
def __init__(self, plex_id):
|
||||
super(Children, self).__init__(
|
||||
'{server}/library/metadata/%s/children' % plex_id)
|
||||
|
||||
|
||||
class Leaves(DownloadGen):
|
||||
"""
|
||||
Iterator object to get all items of a Plex library section
|
||||
"""
|
||||
def __init__(self, section_id):
|
||||
super(PlexSectionItems, self).__init__(
|
||||
'{server}/library/sections/%s/all' % section_id)
|
||||
super(Leaves, self).__init__(
|
||||
'{server}/library/sections/%s/allLeaves' % section_id)
|
||||
|
||||
|
||||
def DownloadChunks(url):
|
||||
|
|
|
@ -355,6 +355,18 @@ PLEX_TYPE_FROM_WEBSOCKET = {
|
|||
15: 'playlist'
|
||||
}
|
||||
|
||||
PLEX_TYPE_NUMBER_FROM_PLEX_TYPE = {
|
||||
PLEX_TYPE_MOVIE: 1,
|
||||
PLEX_TYPE_SHOW: 2,
|
||||
PLEX_TYPE_SEASON: 3,
|
||||
PLEX_TYPE_EPISODE: 4,
|
||||
PLEX_TYPE_ARTIST: 8,
|
||||
PLEX_TYPE_ALBUM: 9,
|
||||
PLEX_TYPE_SONG: 10,
|
||||
PLEX_TYPE_CLIP: 12,
|
||||
'playlist': 15
|
||||
}
|
||||
|
||||
|
||||
KODI_TO_PLEX_ARTWORK = {
|
||||
'poster': 'thumb',
|
||||
|
|
Loading…
Reference in a new issue