Merge branch 'translations' into l10n_translations

This commit is contained in:
tomkat83 2017-04-07 11:44:48 +02:00
commit 318c01deb9
23 changed files with 1024 additions and 884 deletions

131
README.md
View file

@ -1,7 +1,11 @@
##Status
[![stable version](https://img.shields.io/badge/stable_version-1.7.5-blue.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip)
[![beta version](https://img.shields.io/badge/beta_version-1.7.5-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip)
[![GitHub issues](https://img.shields.io/github/issues/croneter/PlexKodiConnect.svg?maxAge=60&style=flat-square)](https://github.com/croneter/PlexKodiConnect/issues)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/croneter/PlexKodiConnect.svg?maxAge=60&style=flat-square)](https://github.com/croneter/PlexKodiConnect/pulls)
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)
[![Forum](https://img.shields.io/badge/forum-plex-orange.svg?maxAge=60&style=flat)](https://forums.plex.tv/discussion/210023/plexkodiconnect-let-kodi-talk-to-your-plex)
[![GitHub issues](https://img.shields.io/github/issues/croneter/PlexKodiConnect.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/issues) [![GitHub pull requests](https://img.shields.io/github/issues-pr/croneter/PlexKodiConnect.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/pulls) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/a66870f19ced4fb98f94d9fd56e34e87)](https://www.codacy.com/app/croneter/PlexKodiConnect?utm_source=github.com&utm_medium=referral&utm_content=croneter/PlexKodiConnect&utm_campaign=Badge_Grade)
# PlexKodiConnect (PKC)
@ -11,93 +15,81 @@ PKC combines the best of Kodi - ultra smooth navigation, beautiful and highly cu
Have a look at [some screenshots](https://github.com/croneter/PlexKodiConnect/wiki/Some-PKC-Screenshots) to see what's possible.
### Call for Translations
### Please Help Translating
Please help translate PlexKodiConnect into your language: [visit crowdin.com](https://crowdin.com/project/plexkodiconnect/invite)
Please help translate PlexKodiConnect into your language: [crowdin.com](https://crowdin.com/project/plexkodiconnect/invite)
### Content
* [**Warning**](#warning)
* [**What does PKC do and how is it different from the official 'Plex for Kodi'**](#what-does-pkc-do-and-how-is-it-different-from-the-official-plex-for-kod)
* [**What does PKC do?**](#what-does-pkc-do)
* [**PKC Features**](#pkc-features)
* [**Download and Installation**](#download-and-installation)
* [**Important notes**](#important-notes)
* [**Donations**](#donations)
* [**What is currently supported?**](#what-is-currently-supported)
* [**Request a New Feature**](#request-a-new-feature)
* [**Known Larger Issues**](#known-larger-issues)
* [**Issues being worked on**](#issues-being-worked-on)
* [**Requests for new features**](#requests-for-new-features)
* [**Checkout the PKC Wiki**](#checkout-the-pkc-wiki)
* [**Credits**](#credits)
### Warning
Use at your own risk! This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases as this plugin directly changes them. Don't worry if you want Plex to manage all your media (like you should ;-)).
### What does PKC do and how is it different from the official ['Plex for Kodi'](https://www.plex.tv/apps/computer/kodi/)?
With other Plex addons for Kodi such as the official [Plex for Kodi](https://www.plex.tv/apps/computer/kodi/) or [PlexBMC](https://forums.plex.tv/discussion/106593/plexbmc-xbmc-add-on-to-connect-to-plex-media-server) there are a couple of issues:
- Other Kodi addons such as NextAired, remote apps and others won't work
- You can only use special Kodi skins
- Slow speed: when browsing data has to be retrieved from the server. Especially on slower devices this can take too much time and you will notice artwork being loaded slowly while you browse the library
- All kinds of workarounds are needed to get the best experience on Kodi clients
PKC synchronizes your media from your Plex server to the native Kodi database. Because PKC uses the native Kodi database, the above limitations are gone!
- Use any Kodi skin you want!
- You can browse your media at full speed, images are cached
- All other Kodi addons will be able to "see" your media, thinking it's normal Kodi stuff
Some people argue that PKC is 'hacky' because of the way it directly accesses the Kodi database. See [here for a more thorough discussion](https://github.com/croneter/PlexKodiConnect/wiki/Is-PKC-'hacky'%3F).
### Download and Installation
[ ![Download](https://api.bintray.com/packages/croneter/PlexKodiConnect/PlexKodiConnect/images/download.svg) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip)
### What does PKC do?
PKC synchronizes your media from your Plex server to the native Kodi database. Hence:
- Use virtually any other Kodi add-on
- Use any Kodi skin, completely customize Kodi's look
- Browse your media at full speed (cached artwork)
- Automatically get additional artwork (more than Plex offers)
- Enjoy Plex features using the Kodi interface
Install PKC via the PlexKodiConnect Kodi repository (we cannot use the official Kodi repository as PKC messes with Kodi's databases). See the [installation guideline on how to do this](https://github.com/croneter/PlexKodiConnect/wiki/Installation).
### PKC Features
**Possibly UNSTABLE BETA version:** [ ![Download](https://api.bintray.com/packages/croneter/PlexKodiConnect_BETA/PlexKodiConnect_BETA/images/download.svg) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip)
### Important Notes
1. If you are using a **low CPU device like a Raspberry Pi or a CuBox**, PKC might be instable or crash during initial sync. Lower the number of threads in the [PKC settings under Sync Options](https://github.com/croneter/PlexKodiConnect/wiki/PKC-settings#sync-options): `Limit artwork cache threads: 5`
Don't forget to reboot Kodi after that.
2. **Compatibility**:
* PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing.
* PKC is not (and will never be) compatible with the **MySQL database replacement** in Kodi. In fact, PKC replaces the MySQL functionality because it acts as a "man in the middle" for your entire media library.
* If **another plugin is not working** like it's supposed to, try to use [PKC direct paths](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Paths)
3. If you post logs, your **Plex tokens** might be included. Be sure to double and triple check for tokens before posting any logs anywhere by searching for `token`
### Donations
I'm not in any way affiliated with Plex. Thank you very much for a small donation via ko-fi.com and PayPal if you appreciate PKC.
**Full disclaimer:** I will see your name and address on my PayPal account. Rest assured that I will not share this with anyone.
[ ![Download](https://az743702.vo.msecnd.net/cdn/kofi1.png?v=a|alt=Buy Me a Coffee)](https://ko-fi.com/A8182EB)
### What is currently supported?
PKC currently provides the following features:
- All Plex library types
+ Movies and Home Videos
+ TV Shows
+ Music
+ Pictures and Photos
- Different PKC interface languages:
- [Amazon Alexa voice recognition](https://www.plex.tv/apps/streaming-devices/amazon-alexa)
- [Plex Watch Later / Plex It!](https://support.plex.tv/hc/en-us/sections/200211783-Plex-It-)
- [Plex Companion](https://support.plex.tv/hc/en-us/sections/200276908-Plex-Companion): fling Plex media (or anything else) from other Plex devices to PlexKodiConnect
- [Plex Transcoding](https://support.plex.tv/hc/en-us/articles/200250377-Transcoding-Media)
- Automatically download more artwork from [Fanart.tv](https://fanart.tv/), just like the Kodi addon [Artwork Downloader](http://kodi.wiki/view/Add-on:Artwork_Downloader)
- Automatically group movies into [movie sets](http://kodi.wiki/view/movie_sets)
- [Direct play](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Play) from network paths (e.g. "\\\\server\\Plex\\movie.mkv"), something unique to PKC
- Delete PMS items from the Kodi context menu
- PKC is available in the following languages:
+ English
+ German
+ Czech, thanks @Pavuucek
+ Spanish, thanks @bartolomesoriano
+ Danish, thanks @FIGHT
+ More coming up: [you can help!](https://crowdin.com/project/plexkodiconnect/invite)
- [Plex Watch Later / Plex It!](https://support.plex.tv/hc/en-us/sections/200211783-Plex-It-)
- [Plex Companion](https://support.plex.tv/hc/en-us/sections/200276908-Plex-Companion): fling Plex media (or anything else) from other Plex devices to PlexKodiConnect
- [Plex Transcoding](https://support.plex.tv/hc/en-us/articles/200250377-Transcoding-Media)
- Automatically download more artwork from [Fanart.tv](https://fanart.tv/), just like the Kodi addon [Artwork Downloader](http://kodi.wiki/view/Add-on:Artwork_Downloader)
+ Banners
+ Disc art
+ Clear logos
+ Landscapes
+ Clear art
+ Extra fanart backgrounds
- Automatically group movies into [movie sets](http://kodi.wiki/view/movie_sets)
- Direct play from network paths (e.g. "\\\\server\\Plex\\movie.mkv") instead of streaming from slow HTTP (e.g. "192.168.1.1:32400"). You have to setup all your Plex libraries to point to such network paths. Do have a look at [the wiki here](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Paths)
- Delete PMS items from the Kodi context menu
+ Italian, thanks @nikkux, @chicco83
+ Dutch, thanks @mvanbaak
+ [Please help translating](https://crowdin.com/project/plexkodiconnect/invite)
### Download and Installation
Install PKC via the PlexKodiConnect Kodi repository below (we cannot use the official Kodi repository as PKC messes with Kodi's databases). See the [github wiki installation manual](https://github.com/croneter/PlexKodiConnect/wiki/Installation) for a detailed guide. Please use the stable version except if you really know what you're doing. Kodi will update PKC automatically.
| Stable version | Beta version |
|----------------|--------------|
| [![stable version](https://img.shields.io/badge/stable_version-latest-blue.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip) | [![beta version](https://img.shields.io/badge/beta_version-latest-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip) |
### Important Notes
1. If you are using a **low CPU device like a Raspberry Pi or a CuBox**, PKC might be instable or crash during initial sync. Lower the number of threads in the [PKC settings under Sync Options](https://github.com/croneter/PlexKodiConnect/wiki/PKC-settings#sync-options). Don't forget to reboot Kodi after that.
2. **Compatibility**:
* PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing.
* PKC is not (and will never be) compatible with the **MySQL database replacement** in Kodi. In fact, PKC replaces the MySQL functionality because it acts as a "man in the middle" for your entire media library.
* If **another plugin is not working** like it's supposed to, try to use [PKC direct paths](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Paths)
### Donations
I'm not in any way affiliated with Plex. Thank you very much for a small donation via ko-fi.com and PayPal if you appreciate PKC.
**Full disclaimer:** I will see your name and address on my PayPal account. Rest assured that I will not share this with anyone.
[![Donations](https://az743702.vo.msecnd.net/cdn/kofi1.png?v=a)](https://ko-fi.com/A8182EB)
### Request a New Feature
[![Feature Requests](http://feathub.com/croneter/PlexKodiConnect?format=svg)](http://feathub.com/croneter/PlexKodiConnect)
### Known Larger Issues
@ -120,13 +112,6 @@ However, some changes to individual items are instantly detected, e.g. if you ma
Have a look at the [Github Issues Page](https://github.com/croneter/PlexKodiConnect/issues). Before you open your own issue, please read [How to report a bug](https://github.com/croneter/PlexKodiConnect/wiki/How-to-Report-A-Bug).
### Requests for new features
[![Feature Requests](http://feathub.com/croneter/PlexKodiConnect?format=svg)](http://feathub.com/croneter/PlexKodiConnect)
### Checkout the PKC Wiki
The [Wiki can be found here](https://github.com/croneter/PlexKodiConnect/wiki) and will hopefully answer all your questions. You can even edit the wiki yourself!
### Credits
- PlexKodiConnect shamelessly uses pretty much all the code of "Emby for Kodi" by the awesome Emby team (see https://github.com/MediaBrowser/plugin.video.emby). Thanks for sharing guys!!

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="1.6.5" provider-name="croneter">
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="1.7.5" provider-name="croneter">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.3.0" />

View file

@ -1,3 +1,37 @@
version 1.7.5
- Dutch translation, thanks @mvanbaak
version 1.7.4 (beta only)
- Show menu item only for appropriate Kodi library: Be careful to start video content through Videos -> Video Addons -> ... and pictures through Pictures -> Picture Addons -> ...
- Fix playback error popup when using Alexa
- New Italian translations, thanks @nikkux, @chicco83
- Update translations
- Rewire Kodi ListItem stuff
- Fix TypeError for setting ListItem streams
- Fix Kodi setContent for images
- Fix AttributeError due to missing Kodi sort methods
version 1.7.3 (beta only)
- Fix KeyError for channels if no media streams
- Move plex node navigation, playback to main thread
- Fix TypeError for malformed browsing xml
- Fix IndexError if we can't get a valid xml from PMS
- Pass 'None' instead of empty string in url args
version 1.7.2
- Fix for some channels not starting playback
version 1.7.1
- Fix Alexa not doing anything
version 1.7.0
- Amazon Alexa support! Be sure to check the Plex Alexa forum first if you encounter issues; there are still many bugs completely unrelated to PKC
- Plex Channels!
- Browse video nodes by folder/path
- Fix IndexError for playqueues
- Update translations
- Code optimization
version 1.6.5 (beta only)
- Plex Channels!
- Browse video nodes by folder/path

View file

@ -63,6 +63,9 @@ class Main():
if mode == 'play':
self.play()
elif mode == 'plex_node':
self.play()
elif mode == 'ondeck':
entrypoint.getOnDeck(itemid,
params.get('type'),
@ -83,9 +86,6 @@ class Main():
entrypoint.getInProgressEpisodes(params['tagname'],
int(params['limit']))
elif mode == 'Plex_Node':
entrypoint.Plex_Node(itemid, params.get('viewOffset'))
elif mode == 'browseplex':
entrypoint.browse_plex(key=params.get('key'),
plex_section_id=params.get('id'))
@ -165,9 +165,12 @@ class Main():
entrypoint.getVideoFiles(plexId, params)
else:
entrypoint.doMainListing()
entrypoint.doMainListing(content_type=params.get('content_type'))
def play(self):
"""
Start up playback_starter in main Python thread
"""
# Put the request into the 'queue'
while window('plex_play_new_item'):
sleep(50)

View file

@ -236,9 +236,9 @@
<string id="30251">Senest tilføjet hjemmevideoer</string><!-- Verified -->
<string id="30252">Senest tilføjet billeder</string><!-- Verified -->
<string id="30253">Favorite Home Videos</string><!-- Verified -->
<string id="30254">Favorite Photos</string><!-- Verified -->
<string id="30255">Favorite Albums</string>
<string id="30253">Favorit hjemmevideoer</string><!-- Verified -->
<string id="30254">Favorit fotos</string><!-- Verified -->
<string id="30255">Favorit albums</string>
<string id="30256">Senest tilføjet musik videoer</string><!-- Verified -->
<string id="30257">Igangværende musikvideoer</string><!-- Verified -->

View file

@ -517,4 +517,4 @@
<string id="39700">Amazon Alexa (spraakherkenning)</string>
<string id="39701">Aktiveer Alexa</string>
<string id="39702">Per map bladeren</string>
</strings>
</strings>

View file

@ -41,4 +41,4 @@
<description lang="dk">Tilslut Kodi til din Plex Media Server. Dette plugin forudsætter, at du administrere alle dine videoer med Plex (og ikke med Kodi). Du kan miste data som allerede er gemt i Kodi video og musik-databaser (dette plugin ændrer direkte i dem). Brug på eget ansvar!</description>
<description lang="nl">Verbind Kodi met je Plex Media Server. Deze plugin gaat ervan uit dat je al je video's met Plex (en niet met Kodi) beheerd. Je kunt gegevens reeds opgeslagen in de databases voor video en muziek van Kodi (deze plugin wijzigt deze gegevens direct) verliezen. Gebruik op eigen risico!</description>
</extension>
</addon>
</addon>

View file

@ -236,9 +236,9 @@
<string id="30251">Video aggiunti di recente</string><!-- Verified -->
<string id="30252">Foto aggiunte di recente</string><!-- Verified -->
<string id="30253">Favorite Home Videos</string><!-- Verified -->
<string id="30254">Favorite Photos</string><!-- Verified -->
<string id="30255">Favorite Albums</string>
<string id="30253">Video preferiti</string><!-- Verified -->
<string id="30254">Foto preferite</string><!-- Verified -->
<string id="30255">Album preferiti</string>
<string id="30256">Video Musicali aggiuni di recente</string><!-- Verified -->
<string id="30257">Video Musicali in corso</string><!-- Verified -->

View file

@ -14,18 +14,23 @@ def convert_PKC_to_listitem(PKC_listitem):
"""
Insert a PKC_listitem and you will receive a valid XBMC listitem
"""
listitem = ListItem()
for func, args in PKC_listitem.data.items():
if isinstance(args, list):
for arg in args:
getattr(listitem, func)(*arg)
elif isinstance(args, dict):
for arg in args.items():
getattr(listitem, func)(*arg)
elif args is None:
continue
else:
getattr(listitem, func)(args)
data = PKC_listitem.data
log.debug('data is: %s' % data)
listitem = ListItem(label=data.get('label'),
label2=data.get('label2'),
path=data.get('path'))
if data['info']:
listitem.setInfo(**data['info'])
for stream in data['stream_info']:
# Kodi documentation up to date? CAREFUL as type= seems to be cType=
# and values= seems to be dictionary=
listitem.addStreamInfo(**stream)
if data['art']:
listitem.setArt(data['art'])
for key, value in data['property'].iteritems():
listitem.setProperty(key, value)
if data['subtitles']:
listitem.setSubtitles(data['subtitles'])
return listitem
@ -38,14 +43,14 @@ class PKC_ListItem(object):
"""
def __init__(self, label=None, label2=None, path=None):
self.data = {
'addStreamInfo': [], # (type, values: dict { label: value })
'setArt': [], # dict: { label: value }
'setInfo': {}, # type: infoLabel (dict { label: value })
'setLabel': label, # string
'setLabel2': label2, # string
'setPath': path, # string
'setProperty': {}, # (key, value)
'setSubtitles': [], # string
'stream_info': [], # (type, values: dict { label: value })
'art': {}, # dict
'info': {}, # type: infoLabel (dict { label: value })
'label': label, # string
'label2': label2, # string
'path': path, # string
'property': {}, # (key, value)
'subtitles': [], # strings
}
def addContextMenuItems(self, items, replaceItems):
@ -87,19 +92,19 @@ class PKC_ListItem(object):
- Subtitle Values:
- language : string (en)
"""
self.data['addStreamInfo'].append((type, values))
self.data['stream_info'].append({'cType': type, 'dictionary': values})
def getLabel(self):
"""
Returns the listitem label
"""
return self.data['setLabel']
return self.data.get('label')
def getLabel2(self):
"""
Returns the listitem label.
"""
return self.data['setLabel2']
return self.data.get('label2')
def getMusicInfoTag(self):
"""
@ -118,7 +123,7 @@ class PKC_ListItem(object):
Once you use a keyword, all following arguments require the keyword.
"""
return self.data['setProperty'].get(key)
return self.data['property'].get(key)
def getVideoInfoTag(self):
"""
@ -172,7 +177,7 @@ class PKC_ListItem(object):
- landscape : string - image filename
- icon : string - image filename
"""
self.data['setArt'].append(values)
self.data['art'].update(values)
def setContentLookup(self, enable):
"""
@ -270,21 +275,21 @@ class PKC_ListItem(object):
- exif : string (See CPictureInfoTag::TranslateString in
PictureInfoTag.cpp for valid strings)
"""
self.data['setInfo'][type] = infoLabels
self.data['info'] = {'type': type, 'infoLabels': infoLabels}
def setLabel(self, label):
"""
Sets the listitem's label.
label : string or unicode - text string.
"""
self.data['setLabel'] = label
self.data['label'] = label
def setLabel2(self, label):
"""
Sets the listitem's label2.
label : string or unicode - text string.
"""
self.data['setLabel2'] = label
self.data['label2'] = label
def setMimeType(self, mimetype):
"""
@ -303,7 +308,7 @@ class PKC_ListItem(object):
*Note, You can use the above as keywords for arguments.
"""
self.data['setPath'] = path
self.data['path'] = path
def setProperty(self, key, value):
"""
@ -321,7 +326,7 @@ class PKC_ListItem(object):
start playback of an item. Others may be used in the skin to add extra
information, such as 'WatchedCount' for tvshow items
"""
self.data['setProperty'][key] = value
self.data['property'][key] = value
def setSubtitles(self, subtitles):
"""
@ -331,4 +336,4 @@ class PKC_ListItem(object):
- listitem.setSubtitles(['special://temp/example.srt',
'http://example.com/example.srt' ])
"""
self.data['setSubtitles'].extend(([subtitles],))
self.data['subtitles'].extend(subtitles)

View file

@ -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
@ -1208,6 +1209,30 @@ class API():
ans = unquote(ans).decode('latin1')
return ans
def get_picture_path(self):
"""
Returns the item's picture path (transcode, if necessary) as string.
Will always use addon paths, never direct paths
"""
extension = self.item[0][0].attrib['key'][self.item[0][0].attrib['key'].rfind('.'):].lower()
if (window('plex_force_transcode_pix') == 'true' or
extension not in v.KODI_SUPPORTED_IMAGES):
# Let Plex transcode
# max width/height supported by plex image transcoder is 1920x1080
path = self.server + PlexAPI().getTranscodeImagePath(
self.item[0][0].attrib.get('key'),
window('pms_token'),
"%s%s" % (self.server, self.item[0][0].attrib.get('key')),
1920,
1080)
else:
path = self.addPlexCredentialsToUrl(
'%s%s' % (window('pms_server'),
self.item[0][0].attrib['key']))
# Attach Plex id to url to let it be picked up by our playqueue agent
# later
return tryEncode('%s&plex_id=%s' % (path, self.getRatingKey()))
def getTVShowPath(self):
"""
Returns the direct path to the TV show, e.g. '\\NAS\tv\series'
@ -2162,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
@ -2275,18 +2325,6 @@ class API():
log.info('Found external subs: %s' % externalsubs)
return externalsubs
def CreateListItemFromPlexItem(self,
listItem=None,
appendShowTitle=False,
appendSxxExx=False):
if self.getType() == 'photo':
listItem = self._createPhotoListItem(listItem)
else:
listItem = self._createVideoListItem(listItem,
appendShowTitle,
appendSxxExx)
return listItem
def GetKodiPremierDate(self):
"""
Takes Plex' originallyAvailableAt of the form "yyyy-mm-dd" and returns
@ -2301,7 +2339,24 @@ class API():
date = None
return date
def _createPhotoListItem(self, listItem=None):
def CreateListItemFromPlexItem(self,
listItem=None,
appendShowTitle=False,
appendSxxExx=False):
if self.getType() == v.PLEX_TYPE_PHOTO:
listItem = self.__createPhotoListItem(listItem)
# Only set the bare minimum of artwork
listItem.setArt({'icon': 'DefaultPicture.png',
'fanart': self.__getOneArtwork('thumb')})
else:
listItem = self.__createVideoListItem(listItem,
appendShowTitle,
appendSxxExx)
self.add_video_streams(listItem)
self.set_listitem_artwork(listItem)
return listItem
def __createPhotoListItem(self, listItem=None):
"""
Use for photo items only
"""
@ -2310,63 +2365,21 @@ class API():
listItem = xbmcgui.ListItem(title)
else:
listItem.setLabel(title)
listItem.setProperty('IsPlayable', 'true')
extension = self.item[0][0].attrib['key'][self.item[0][0].attrib['key'].rfind('.'):].lower()
if (window('plex_force_transcode_pix') == 'true' or
extension not in v.KODI_SUPPORTED_IMAGES):
# Let Plex transcode
# max width/height supported by plex image transcoder is 1920x1080
path = self.server + PlexAPI().getTranscodeImagePath(
self.item[0][0].attrib.get('key'),
window('pms_token'),
"%s%s" % (self.server, self.item[0][0].attrib.get('key')),
1920,
1080)
else:
# Don't transcode
if window('useDirectPaths') == 'true':
# Addon Mode. Just give the path of the file to Kodi
path = self.addPlexCredentialsToUrl(
'%s%s' % (window('pms_server'),
self.item[0][0].attrib['key']))
else:
# Native direct paths
path = self.validatePlayurl(
self.getFilePath(forceFirstMediaStream=True),
'photo')
path = tryEncode(path)
metadata = {
'date': self.GetKodiPremierDate(),
'picturepath': path,
'size': long(self.item[0][0].attrib.get('size', 0)),
'exif:width': self.item[0].attrib.get('width', ''),
'exif:height': self.item[0].attrib.get('height', ''),
'title': title
}
listItem.setInfo('pictures', infoLabels=metadata)
try:
if int(metadata['exif:width']) > int(metadata['exif:height']):
# add image as fanart for use with skinhelper auto thumb/
# backgrund creation
listItem.setArt({'fanart': path})
except ValueError:
pass
# Stuff that we CANNOT set with listItem.setInfo
listItem.setProperty('path', path)
listItem.setInfo(type='image', infoLabels=metadata)
listItem.setProperty('plot', self.getPlot())
listItem.setProperty('plexid', self.getRatingKey())
# We do NOT set these props
# listItem.setProperty('isPlayable', 'true')
# listItem.setProperty('isFolder', 'true')
# Further stuff
listItem.setArt({'icon': 'DefaultPicture.png'})
return listItem
def _createVideoListItem(self,
listItem=None,
appendShowTitle=False,
appendSxxExx=False):
def __createVideoListItem(self,
listItem=None,
appendShowTitle=False,
appendSxxExx=False):
"""
Use for video items only
Call on a child level of PMS xml response (e.g. in a for loop)
@ -2383,8 +2396,10 @@ class API():
if listItem is None:
listItem = xbmcgui.ListItem(title)
else:
listItem.setLabel(title)
# Necessary; Kodi won't start video otherwise!
listItem.setProperty('IsPlayable', 'true')
# Video items, e.g. movies and episodes or clips
people = self.getPeople()
userdata = self.getUserData()
@ -2410,8 +2425,7 @@ class API():
listItem.setProperty('resumetime', str(userdata['Resume']))
listItem.setProperty('totaltime', str(userdata['Runtime']))
if typus == "episode":
# Only for tv shows
if typus == v.PLEX_TYPE_EPISODE:
key, show, season, episode = self.getEpisodeDetails()
season = -1 if season is None else int(season)
episode = -1 if episode is None else int(episode)
@ -2426,7 +2440,9 @@ class API():
listItem.setArt({'icon': 'DefaultTVShows.png'})
if appendShowTitle is True:
title = "%s - %s " % (show, title)
elif typus == "movie":
if appendShowTitle or appendSxxExx:
listItem.setLabel(title)
elif typus == v.PLEX_TYPE_MOVIE:
listItem.setArt({'icon': 'DefaultMovies.png'})
else:
# E.g. clips, trailers, ...
@ -2442,11 +2458,10 @@ class API():
pass
# Expensive operation
metadata['title'] = title
listItem.setLabel(title)
listItem.setInfo('video', infoLabels=metadata)
return listItem
def AddStreamInfo(self, listItem):
def add_video_streams(self, listItem):
"""
Add media stream information to xbmcgui.ListItem
"""

View file

@ -3,20 +3,18 @@ import logging
from threading import Thread
import Queue
from socket import SHUT_RDWR
from urllib import urlencode
from xbmc import sleep
from xbmc import sleep, executebuiltin
from utils import settings, ThreadMethodsAdditionalSuspend, ThreadMethods, \
window
from utils import settings, ThreadMethodsAdditionalSuspend, ThreadMethods
from plexbmchelper import listener, plexgdm, subscribers, functions, \
httppersist, plexsettings
from PlexFunctions import ParseContainerKey, GetPlexMetadata
from PlexAPI import API
import player
from entrypoint import Plex_Node
import variables as v
###############################################################################
log = logging.getLogger("PLEX."+__name__)
@ -93,23 +91,27 @@ class PlexCompanion(Thread):
self.mgr.playqueue.init_playqueue_from_plex_children(
api.getRatingKey())
else:
thread = Thread(target=Plex_Node,
args=('{server}%s' % data.get('key'),
data.get('offset'),
True,
False),)
thread.setDaemon(True)
thread.start()
params = {
'mode': 'plex_node',
'key': '{server}%s' % data.get('key'),
'view_offset': data.get('offset'),
'play_directly': 'true',
'node': 'false'
}
executebuiltin('RunPlugin(plugin://%s?%s)'
% (v.ADDON_ID, urlencode(params)))
elif (task['action'] == 'playlist' and
data.get('address') == 'node.plexapp.com'):
# E.g. watch later initiated by Companion
thread = Thread(target=Plex_Node,
args=('{server}%s' % data.get('key'),
data.get('offset'),
True),)
thread.setDaemon(True)
thread.start()
params = {
'mode': 'plex_node',
'key': '{server}%s' % data.get('key'),
'view_offset': data.get('offset'),
'play_directly': 'true'
}
executebuiltin('RunPlugin(plugin://%s?%s)'
% (v.ADDON_ID, urlencode(params)))
elif task['action'] == 'playlist':
# Get the playqueue ID

View file

@ -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):

View file

@ -6,21 +6,16 @@ from sys import argv
from urllib import urlencode
import xbmcplugin
from xbmc import sleep, Player, executebuiltin, getCondVisibility, \
translatePath
from xbmc import sleep, executebuiltin, translatePath
from xbmcgui import ListItem
from utils import window, settings, language as lang, dialog, tryDecode,\
tryEncode, CatchExceptions, JSONRPC
import downloadutils
import playbackutils as pbutils
import plexdb_functions as plexdb
from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \
GetMachineIdentifier
from PlexAPI import API
from PKC_listitem import convert_PKC_to_listitem
from playqueue import Playqueue
import variables as v
###############################################################################
@ -97,62 +92,6 @@ def togglePlexTV():
sound=False)
def Plex_Node(url, viewOffset, playdirectly=False, node=True):
"""
Called only for a SINGLE element for Plex.tv watch later
Always to return with a "setResolvedUrl"
"""
log.info('Plex_Node called with url: %s, viewOffset: %s'
% (url, viewOffset))
# Plex redirect, e.g. watch later. Need to get actual URLs
if url.startswith('http'):
xml = downloadutils.DownloadUtils().downloadUrl(url)
else:
xml = downloadutils.DownloadUtils().downloadUrl('{server}%s' % url)
try:
xml[0].attrib
except:
log.error('Could not download PMS metadata')
return
if viewOffset != '0':
try:
viewOffset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(viewOffset))
except:
pass
else:
window('plex_customplaylist.seektime', value=str(viewOffset))
log.info('Set resume point to %s' % str(viewOffset))
api = API(xml[0])
typus = v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()]
if node is True:
plex_id = None
kodi_id = 'plexnode'
else:
plex_id = api.getRatingKey()
kodi_id = None
with plexdb.Get_Plex_DB() as plex_db:
plexdb_item = plex_db.getItem_byId(plex_id)
try:
kodi_id = plexdb_item[0]
except TypeError:
log.info('Couldnt find item %s in Kodi db'
% api.getRatingKey())
playqueue = Playqueue().get_playqueue_from_type(typus)
result = pbutils.PlaybackUtils(xml, playqueue).play(
plex_id,
kodi_id=kodi_id,
plex_lib_UUID=xml.attrib.get('librarySectionUUID'))
if result.listitem:
listitem = convert_PKC_to_listitem(result.listitem)
else:
return
if playdirectly:
Player().play(listitem.getfilename(), listitem)
else:
xbmcplugin.setResolvedUrl(HANDLE, True, listitem)
##### DO RESET AUTH #####
def resetAuth():
# User tried login and failed too many times
@ -172,7 +111,8 @@ def addDirectoryItem(label, path, folder=True):
xbmcplugin.addDirectoryItem(handle=HANDLE, url=path, listitem=li, isFolder=folder)
def doMainListing():
def doMainListing(content_type=None):
log.debug('Do main listing with content_type: %s' % content_type)
xbmcplugin.setContent(HANDLE, 'files')
# Get emby nodes from the window props
plexprops = window('Plex.nodes.total')
@ -182,37 +122,38 @@ def doMainListing():
path = window('Plex.nodes.%s.index' % i)
if not path:
path = window('Plex.nodes.%s.content' % i)
if not path:
continue
label = window('Plex.nodes.%s.title' % i)
node_type = window('Plex.nodes.%s.type' % i)
#because we do not use seperate entrypoints for each content type, we need to figure out which items to show in each listing.
#for now we just only show picture nodes in the picture library video nodes in the video library and all nodes in any other window
if path and getCondVisibility("Window.IsActive(Pictures)") and node_type == "photos":
# because we do not use seperate entrypoints for each content type,
# we need to figure out which items to show in each listing. for
# now we just only show picture nodes in the picture library video
# nodes in the video library and all nodes in any other window
if node_type == 'photos' and content_type == 'image':
addDirectoryItem(label, path)
elif path and getCondVisibility("Window.IsActive(VideoLibrary)") and node_type != "photos":
addDirectoryItem(label, path)
elif path and not getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"):
elif (node_type != 'photos' and
content_type not in ('image', 'audio')):
addDirectoryItem(label, path)
# Plex Watch later
addDirectoryItem(lang(39211),
"plugin://plugin.video.plexkodiconnect/?mode=watchlater")
if content_type not in ('image', 'audio'):
addDirectoryItem(lang(39211),
"plugin://%s?mode=watchlater" % v.ADDON_ID)
# Plex Channels
addDirectoryItem(lang(30173),
"plugin://plugin.video.plexkodiconnect/?mode=channels")
"plugin://%s?mode=channels" % v.ADDON_ID)
# Plex user switch
addDirectoryItem(lang(39200) + window('plex_username'),
"plugin://plugin.video.plexkodiconnect/"
"?mode=switchuser")
"plugin://%s?mode=switchuser" % v.ADDON_ID)
#experimental live tv nodes
# addDirectoryItem("Live Tv Channels (experimental)", "plugin://plugin.video.plexkodiconnect/?mode=browsecontent&type=tvchannels&folderid=root")
# addDirectoryItem("Live Tv Recordings (experimental)", "plugin://plugin.video.plexkodiconnect/?mode=browsecontent&type=recordings&folderid=root")
# some extra entries for settings and stuff. TODO --> localize the labels
addDirectoryItem(lang(39201), "plugin://plugin.video.plexkodiconnect/?mode=settings")
# addDirectoryItem("Add user to session", "plugin://plugin.video.plexkodiconnect/?mode=adduser")
addDirectoryItem(lang(39203), "plugin://plugin.video.plexkodiconnect/?mode=refreshplaylist")
addDirectoryItem(lang(39204), "plugin://plugin.video.plexkodiconnect/?mode=manualsync")
# some extra entries for settings and stuff
addDirectoryItem(lang(39201),
"plugin://%s?mode=settings" % v.ADDON_ID)
addDirectoryItem(lang(39203),
"plugin://%s?mode=refreshplaylist" % v.ADDON_ID)
addDirectoryItem(lang(39204),
"plugin://%s?mode=manualsync" % v.ADDON_ID)
xbmcplugin.endOfDirectory(HANDLE)
@ -680,8 +621,6 @@ def getOnDeck(viewid, mediatype, tagname, limit):
listitem = api.CreateListItemFromPlexItem(
appendShowTitle=appendShowTitle,
appendSxxExx=appendSxxExx)
api.AddStreamInfo(listitem)
api.set_listitem_artwork(listitem)
if directpaths:
url = api.getFilePath()
else:
@ -852,12 +791,10 @@ 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):
except (ValueError, AttributeError, IndexError, TypeError):
log.error('Could not browse to %s' % key)
return xbmcplugin.endOfDirectory(HANDLE, False)
@ -871,10 +808,10 @@ def browse_plex(key=None, plex_section_id=None):
albums = False
musicvideos = False
for item in xml:
typus = item.attrib.get('type')
if item.tag == 'Directory':
__build_folder(item, plex_section_id=plex_section_id)
else:
typus = item.attrib.get('type')
__build_item(item)
if typus == v.PLEX_TYPE_PHOTO:
photos = True
@ -903,7 +840,7 @@ def browse_plex(key=None, plex_section_id=None):
xbmcplugin.setContent(HANDLE, 'movies')
sort_methods = v.SORT_METHODS_CLIPS
elif photos is True:
xbmcplugin.setContent(HANDLE, 'files')
xbmcplugin.setContent(HANDLE, 'images')
sort_methods = v.SORT_METHODS_PHOTOS
elif tvshows is True:
xbmcplugin.setContent(HANDLE, 'tvshows')
@ -961,23 +898,24 @@ def __build_folder(xml_element, plex_section_id=None):
def __build_item(xml_element):
api = API(xml_element)
listitem = api.CreateListItemFromPlexItem()
api.AddStreamInfo(listitem)
api.set_listitem_artwork(listitem)
if api.getType() == v.PLEX_TYPE_CLIP:
if (api.getKey().startswith('/system/services') or
api.getKey().startswith('http')):
params = {
'mode': "Plex_Node",
'id': xml_element.attrib.get('key'),
'viewOffset': xml_element.attrib.get('viewOffset', '0'),
'plex_type': xml_element.attrib.get('type')
'mode': 'plex_node',
'key': xml_element.attrib.get('key'),
'view_offset': xml_element.attrib.get('viewOffset', '0'),
}
url = "plugin://%s?%s" % (v.ADDON_ID, urlencode(params))
elif api.getType() == v.PLEX_TYPE_PHOTO:
url = api.get_picture_path()
else:
params = {
'mode': 'play',
'filename': api.getKey(),
'id': api.getRatingKey(),
'dbid': listitem.getProperty('dbid') or '',
'mode': "play"
'dbid': listitem.getProperty('dbid')
}
url = "plugin://%s?%s" % (v.ADDON_ID, urlencode(params))
url = "plugin://%s?%s" % (v.ADDON_ID, urlencode(params))
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=url,
listitem=listitem)

View file

@ -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)
artists = item.attrib.get('grandparentTitle')
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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@
###############################################################################
import logging
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:

View file

@ -4,6 +4,8 @@ import logging
from threading import Thread
from urlparse import parse_qsl
from xbmc import Player
from PKC_listitem import PKC_ListItem
from pickler import pickle_me, Playback_Successful
from playbackutils import PlaybackUtils
@ -12,6 +14,9 @@ from PlexFunctions import GetPlexMetadata
from PlexAPI import API
from playqueue import lock
import variables as v
from downloadutils import DownloadUtils
from PKC_listitem import convert_PKC_to_listitem
import plexdb_functions as plexdb
###############################################################################
log = logging.getLogger("PLEX."+__name__)
@ -41,7 +46,7 @@ class Playback_Starter(Thread):
xml = GetPlexMetadata(plex_id)
try:
xml[0].attrib
except (TypeError, AttributeError):
except (IndexError, TypeError, AttributeError):
log.error('Could not get a PMS xml for plex id %s' % plex_id)
return
api = API(xml[0])
@ -50,8 +55,6 @@ class Playback_Starter(Thread):
result = Playback_Successful()
listitem = PKC_ListItem()
listitem = api.CreateListItemFromPlexItem(listitem)
api.AddStreamInfo(listitem)
api.set_listitem_artwork(listitem)
result.listitem = listitem
else:
# Video and Music
@ -66,9 +69,65 @@ class Playback_Starter(Thread):
% self.playqueue.playqueues)
return result
def process_plex_node(self, url, viewOffset, directplay=False,
node=True):
"""
Called for Plex directories or redirect for playback (e.g. trailers,
clips, watchlater)
"""
log.info('process_plex_node called with url: %s, viewOffset: %s'
% (url, viewOffset))
# Plex redirect, e.g. watch later. Need to get actual URLs
if url.startswith('http') or url.startswith('{server}'):
xml = DownloadUtils().downloadUrl(url)
else:
xml = DownloadUtils().downloadUrl('{server}%s' % url)
try:
xml[0].attrib
except:
log.error('Could not download PMS metadata')
return
if viewOffset != '0':
try:
viewOffset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(viewOffset))
except:
pass
else:
window('plex_customplaylist.seektime', value=str(viewOffset))
log.info('Set resume point to %s' % str(viewOffset))
api = API(xml[0])
typus = v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()]
if node is True:
plex_id = None
kodi_id = 'plexnode'
else:
plex_id = api.getRatingKey()
kodi_id = None
with plexdb.Get_Plex_DB() as plex_db:
plexdb_item = plex_db.getItem_byId(plex_id)
try:
kodi_id = plexdb_item[0]
except TypeError:
log.info('Couldnt find item %s in Kodi db'
% api.getRatingKey())
playqueue = self.playqueue.get_playqueue_from_type(typus)
with lock:
result = PlaybackUtils(xml, playqueue).play(
plex_id,
kodi_id=kodi_id,
plex_lib_UUID=xml.attrib.get('librarySectionUUID'))
if directplay:
if result.listitem:
listitem = convert_PKC_to_listitem(result.listitem)
Player().play(listitem.getfilename(), listitem)
return Playback_Successful()
else:
return result
def triage(self, item):
mode, params = item.split('?', 1)
_, params = item.split('?', 1)
params = dict(parse_qsl(params))
mode = params.get('mode')
log.debug('Received mode: %s, params: %s' % (mode, params))
try:
if mode == 'play':
@ -76,6 +135,12 @@ class Playback_Starter(Thread):
params.get('dbid'))
elif mode == 'companion':
result = self.process_companion()
elif mode == 'plex_node':
result = self.process_plex_node(
params.get('key'),
params.get('view_offset'),
directplay=True if params.get('play_directly') else False,
node=False if params.get('node') == 'false' else True)
except:
log.error('Error encountered for mode %s, params %s'
% (mode, params))

View file

@ -1,9 +1,10 @@
import logging
from urllib import quote
from urlparse import parse_qsl, urlsplit
import plexdb_functions as plexdb
from downloadutils import DownloadUtils as DU
from utils import JSONRPC, tryEncode, tryDecode
from utils import JSONRPC, tryEncode
from PlexAPI import API
###############################################################################
@ -111,6 +112,9 @@ def playlist_item_from_kodi(kodi_item):
except TypeError:
pass
item.file = kodi_item.get('file')
if item.file is not None and item.plex_id is None:
item.plex_id = dict(
parse_qsl(urlsplit(item.file).query)).get('plex_id')
item.kodi_type = kodi_item.get('type')
if item.plex_id is None:
item.uri = 'library://whatever/item/%s' % quote(item.file, safe='')
@ -164,17 +168,6 @@ def playlist_item_from_xml(playlist, xml_video_element):
return item
def _log_xml(xml):
try:
xml.attrib
except AttributeError:
log.error('Did not receive an XML. Answer was: %s' % xml)
else:
from xml.etree.ElementTree import dump
log.error('XML received from the PMS:')
dump(xml)
def _get_playListVersion_from_xml(playlist, xml):
"""
Takes a PMS xml as input to overwrite the playlist version (e.g. Plex
@ -185,7 +178,6 @@ def _get_playListVersion_from_xml(playlist, xml):
except (TypeError, AttributeError, KeyError):
log.error('Could not get new playlist Version for playlist %s'
% playlist)
_log_xml(xml)
return False
return True
@ -208,7 +200,6 @@ def get_playlist_details_from_xml(playlist, xml):
% playlist)
import traceback
log.error(traceback.format_exc())
_log_xml(xml)
raise KeyError
log.debug('Updated playlist from xml: %s' % playlist)
@ -341,7 +332,6 @@ def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None):
except (TypeError, AttributeError, KeyError):
log.error('Could not add item %s to playlist %s'
% (kodi_item, playlist))
_log_xml(xml)
return
# Get the guid for this item
for plex_item in xml:

View file

@ -329,7 +329,6 @@ SORT_METHODS_SONGS = (
'SORT_METHOD_TRACKNUM',
'SORT_METHOD_DURATION',
'SORT_METHOD_ARTIST',
'SORT_METHOD_ARTIST_AND_YEAR',
'SORT_METHOD_ALBUM',
'SORT_METHOD_SONG_RATING',
'SORT_METHOD_SONG_USER_RATING'
@ -341,7 +340,6 @@ SORT_METHODS_ARTISTS = (
'SORT_METHOD_TRACKNUM',
'SORT_METHOD_DURATION',
'SORT_METHOD_ARTIST',
'SORT_METHOD_ARTIST_AND_YEAR',
'SORT_METHOD_ALBUM',
)
@ -351,6 +349,5 @@ SORT_METHODS_ALBUMS = (
'SORT_METHOD_TRACKNUM',
'SORT_METHOD_DURATION',
'SORT_METHOD_ARTIST',
'SORT_METHOD_ARTIST_AND_YEAR',
'SORT_METHOD_ALBUM',
)