2018-07-12 05:24:27 +10:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
|
|
Create and delete playlists on the Kodi side of things
|
|
|
|
"""
|
2018-07-13 02:46:02 +10:00
|
|
|
from __future__ import absolute_import, division, unicode_literals
|
2018-07-12 05:24:27 +10:00
|
|
|
from logging import getLogger
|
2018-09-24 00:03:50 +10:00
|
|
|
import re
|
2018-07-12 05:24:27 +10:00
|
|
|
|
|
|
|
from .common import Playlist, PlaylistError
|
|
|
|
from . import db, pms
|
|
|
|
|
|
|
|
from ..plex_api import API
|
|
|
|
from .. import utils, path_ops, variables as v
|
|
|
|
###############################################################################
|
|
|
|
LOG = getLogger('PLEX.playlists.kodi_pl')
|
|
|
|
|
|
|
|
###############################################################################
|
|
|
|
|
2019-02-09 01:15:52 +11:00
|
|
|
REGEX_FILE_NUMBERING = re.compile(r'''_(\d\d)\.\w+$''')
|
2018-09-24 00:03:50 +10:00
|
|
|
|
2018-07-12 05:24:27 +10:00
|
|
|
|
|
|
|
def create(plex_id):
|
|
|
|
"""
|
|
|
|
Creates a new Kodi playlist file. Will also add (or modify an existing)
|
|
|
|
Plex playlist table entry.
|
|
|
|
Assumes that the Plex playlist is indeed new. A NEW Kodi playlist will be
|
|
|
|
created in any case (not replaced). Thus make sure that the "same" playlist
|
|
|
|
is deleted from both disk and the Plex database.
|
|
|
|
Returns the playlist or raises PlaylistError
|
|
|
|
"""
|
|
|
|
xml_metadata = pms.metadata(plex_id)
|
|
|
|
if xml_metadata is None:
|
|
|
|
LOG.error('Could not get Plex playlist metadata %s', plex_id)
|
|
|
|
raise PlaylistError('Could not get Plex playlist %s' % plex_id)
|
|
|
|
api = API(xml_metadata[0])
|
|
|
|
playlist = Playlist()
|
|
|
|
playlist.plex_id = api.plex_id()
|
|
|
|
playlist.kodi_type = v.KODI_PLAYLIST_TYPE_FROM_PLEX[api.playlist_type()]
|
|
|
|
playlist.plex_name = api.title()
|
|
|
|
playlist.plex_updatedat = api.updated_at()
|
|
|
|
LOG.debug('Creating new Kodi playlist from Plex playlist: %s', playlist)
|
|
|
|
# Derive filename close to Plex playlist name
|
|
|
|
name = utils.valid_filename(playlist.plex_name)
|
|
|
|
path = path_ops.path.join(v.PLAYLIST_PATH, playlist.kodi_type,
|
|
|
|
'%s.m3u' % name)
|
|
|
|
while path_ops.exists(path) or db.get_playlist(path=path):
|
|
|
|
# In case the Plex playlist names are not unique
|
2018-09-24 00:03:50 +10:00
|
|
|
occurance = REGEX_FILE_NUMBERING.search(path)
|
2018-07-12 05:24:27 +10:00
|
|
|
if not occurance:
|
|
|
|
path = path_ops.path.join(v.PLAYLIST_PATH,
|
|
|
|
playlist.kodi_type,
|
|
|
|
'%s_01.m3u' % name[:min(len(name), 248)])
|
|
|
|
else:
|
2018-09-24 00:03:50 +10:00
|
|
|
number = int(occurance.group(1)) + 1
|
2019-02-09 01:15:52 +11:00
|
|
|
if number > 3:
|
|
|
|
LOG.error('Detected spanning tree issue, abort sync for %s',
|
|
|
|
playlist)
|
|
|
|
raise PlaylistError('Spanning tree warning')
|
2018-09-24 00:03:50 +10:00
|
|
|
basename = re.sub(REGEX_FILE_NUMBERING, '', path)
|
|
|
|
path = '%s_%02d.m3u' % (basename, number)
|
2018-07-12 05:24:27 +10:00
|
|
|
LOG.debug('Kodi playlist path: %s', path)
|
|
|
|
playlist.kodi_path = path
|
|
|
|
xml_playlist = pms.get_playlist(plex_id)
|
|
|
|
if xml_playlist is None:
|
|
|
|
LOG.error('Could not get Plex playlist %s', plex_id)
|
|
|
|
raise PlaylistError('Could not get Plex playlist %s' % plex_id)
|
|
|
|
_write_playlist_to_file(playlist, xml_playlist)
|
|
|
|
playlist.kodi_hash = utils.generate_file_md5(path)
|
|
|
|
db.update_playlist(playlist)
|
|
|
|
LOG.debug('Created Kodi playlist based on Plex playlist: %s', playlist)
|
|
|
|
|
|
|
|
|
|
|
|
def delete(playlist):
|
|
|
|
"""
|
|
|
|
Removes the corresponding Kodi file for playlist Playlist from
|
|
|
|
disk. Be sure that playlist.kodi_path is set. Will also delete the entry in
|
|
|
|
the Plex playlist table.
|
|
|
|
Returns None or raises PlaylistError
|
|
|
|
"""
|
|
|
|
if path_ops.exists(playlist.kodi_path):
|
|
|
|
try:
|
|
|
|
path_ops.remove(playlist.kodi_path)
|
2018-07-16 03:20:51 +10:00
|
|
|
LOG.debug('Deleted Kodi playlist: %s', playlist)
|
2018-07-12 05:24:27 +10:00
|
|
|
except (OSError, IOError) as err:
|
|
|
|
LOG.error('Could not delete Kodi playlist file %s. Error:\n%s: %s',
|
|
|
|
playlist, err.errno, err.strerror)
|
|
|
|
raise PlaylistError('Could not delete %s' % playlist.kodi_path)
|
|
|
|
db.update_playlist(playlist, delete=True)
|
|
|
|
|
|
|
|
|
|
|
|
def _write_playlist_to_file(playlist, xml):
|
|
|
|
"""
|
|
|
|
Feed with playlist Playlist. Will write the playlist to a m3u file
|
|
|
|
Returns None or raises PlaylistError
|
|
|
|
"""
|
|
|
|
text = '#EXTCPlayListM3U::M3U\n'
|
|
|
|
for element in xml:
|
|
|
|
api = API(element)
|
|
|
|
append_season_episode = False
|
|
|
|
if api.plex_type() == v.PLEX_TYPE_EPISODE:
|
2018-10-22 01:56:13 +11:00
|
|
|
_, _, show, season_no, episode_no = api.episode_data()
|
2018-07-12 05:24:27 +10:00
|
|
|
try:
|
2018-10-22 01:56:13 +11:00
|
|
|
season_no = int(season_no)
|
|
|
|
episode_no = int(episode_no)
|
2018-07-12 05:24:27 +10:00
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
append_season_episode = True
|
|
|
|
if append_season_episode:
|
|
|
|
text += ('#EXTINF:%s,%s S%.2dE%.2d - %s\n%s\n'
|
2018-10-22 01:56:13 +11:00
|
|
|
% (api.runtime(), show, season_no, episode_no,
|
2018-07-12 05:24:27 +10:00
|
|
|
api.title(), api.path()))
|
|
|
|
else:
|
|
|
|
# Only append the TV show name
|
|
|
|
text += ('#EXTINF:%s,%s - %s\n%s\n'
|
|
|
|
% (api.runtime(), show, api.title(), api.path()))
|
|
|
|
else:
|
|
|
|
text += ('#EXTINF:%s,%s\n%s\n'
|
|
|
|
% (api.runtime(), api.title(), api.path()))
|
|
|
|
text += '\n'
|
2018-08-08 02:11:23 +10:00
|
|
|
text = text.encode(v.M3U_ENCODING, 'ignore')
|
2018-07-12 05:24:27 +10:00
|
|
|
try:
|
|
|
|
with open(path_ops.encode_path(playlist.kodi_path), 'wb') as f:
|
|
|
|
f.write(text)
|
|
|
|
except EnvironmentError as err:
|
|
|
|
LOG.error('Could not write Kodi playlist file: %s', playlist)
|
|
|
|
LOG.error('Error message %s: %s', err.errno, err.strerror)
|
|
|
|
raise PlaylistError('Cannot write Kodi playlist to path for %s'
|
|
|
|
% playlist)
|