Merge branch 'master' into translations
This commit is contained in:
commit
ccfb32bc0d
14 changed files with 322 additions and 320 deletions
131
README.md
131
README.md
|
@ -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!!
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="1.7.0" 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" />
|
||||
|
|
|
@ -1,3 +1,29 @@
|
|||
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!
|
||||
|
|
11
default.py
11
default.py
|
@ -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)
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1208,6 +1208,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'
|
||||
|
@ -2275,18 +2299,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 +2313,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 +2339,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 +2370,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 +2399,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 +2414,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 +2432,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
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
@ -857,7 +796,7 @@ def browse_plex(key=None, plex_section_id=None):
|
|||
containerSize=int(settings('limitindex')))
|
||||
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 +810,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 +842,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 +900,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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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',
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue