commit
34af90bef8
38 changed files with 4814 additions and 3970 deletions
109
README.md
109
README.md
|
@ -1,88 +1,111 @@
|
||||||
###IMPORTANT###
|
# PlexKodiConnect (PKC)
|
||||||
|
**Combine the best frontend media player Kodi with the best multimedia backend server Plex**
|
||||||
|
|
||||||
1. If you post logs, your **Plex tokens** might be included. Be sure to double and tripple check for tokens before posting any logs anywhere.
|
PKC combines the best of Kodi - ultra smooth navigation, beautiful and highly customizable user interfaces and playback of any file under the sun, and the Plex Media Server to manage all your media without lifting a finger.
|
||||||
2. **Compatibility**: PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing.
|
|
||||||
|
Have a look at [some screenshots](https://github.com/croneter/PlexKodiConnect/wiki/Some-PKC-Screenshots) to see what's possible.
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
|
||||||
|
The easiest way to install PKC is via our 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).
|
||||||
|
|
||||||
|
**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)
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
|
||||||
|
### IMPORTANT NOTES
|
||||||
|
|
||||||
|
1. If your 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. If you post logs, your **Plex tokens** might be included. Be sure to double and tripple check for tokens before posting any logs anywhere by searching for `token`
|
||||||
|
3. **Compatibility**: PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing.
|
||||||
|
|
||||||
|
|
||||||
### [Checkout the Wiki](https://github.com/croneter/PlexKodiConnect/wiki)
|
### Checkout the PKC Wiki
|
||||||
[The Wiki will hopefully answer all your questions](https://github.com/croneter/PlexKodiConnect/wiki)
|
The [Wiki can be found here](https://github.com/croneter/PlexKodiConnect/wiki) and will hopefully answer all your questions.
|
||||||
|
|
||||||
### Welcome to PlexKodiConnect
|
|
||||||
**Connect your Plex Media Server to a Kodi Front End**
|
|
||||||
|
|
||||||
PlexKodiConnect combines the best of Kodi - ultra smooth navigation, beautiful UIs and playback of any file under the sun, and Plex to manage all your media without lifting a finger.
|
|
||||||
|
|
||||||
|
|
||||||
**What does it do?**
|
### What does PKC do?
|
||||||
|
|
||||||
With other addons for Kodi there are a couple of issues:
|
With other addons for Kodi there are a couple of issues:
|
||||||
- 3rd party addons such as NextAired, remote apps etc. won't work
|
- 3rd party addons such as NextAired, remote apps etc. won't work
|
||||||
- Slow speed: when browsing the data has to be retrieved from the server. Especially on slower devices this can take too much time.
|
- Slow speed: when browsing the data has to be retrieved from the server. Especially on slower devices this can take too much time
|
||||||
- You can only use special Kodi skins
|
- You can only use special Kodi skins
|
||||||
- All kinds of workarounds are needed to get the best experience on Kodi clients
|
- All kinds of workarounds are needed to get the best experience on Kodi clients
|
||||||
|
|
||||||
This addon synchronizes your media on your Plex server to the native Kodi database. Because we use the native Kodi database with this new approach the above limitations are gone!
|
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!
|
||||||
- You can browse your media full speed, e.g. images are cached
|
- You can browse your media full speed, images are cached
|
||||||
- All other Kodi addons will be able to "see" your media, thinking it's normal Kodi stuff
|
- All other Kodi addons will be able to "see" your media, thinking it's normal Kodi stuff
|
||||||
- Use any Kodi skin you want!
|
- Use any Kodi skin you want!
|
||||||
|
|
||||||
|
|
||||||
**Installation in Kodi**
|
### What is currently supported?
|
||||||
|
|
||||||
Check out the [Wiki for installation instructions](https://github.com/croneter/PlexKodiConnect/wiki)
|
PKC currently provides the following features:
|
||||||
|
- All Plex library types
|
||||||
|
+ Movies and Home Videos
|
||||||
**What is currently supported?**
|
+ TV Shows
|
||||||
|
+ Music
|
||||||
Currently these features are working:
|
+ Pictures and Photos
|
||||||
- Movies and Home Videos
|
- Different PKC interface languages:
|
||||||
- TV Shows
|
+ English
|
||||||
|
+ German
|
||||||
|
+ More coming up
|
||||||
- [Plex Watch Later / Plex It!](https://support.plex.tv/hc/en-us/sections/200211783-Plex-It-)
|
- [Plex Watch Later / Plex It!](https://support.plex.tv/hc/en-us/sections/200211783-Plex-It-)
|
||||||
- Full sync at first run, then periodic delta syncs every 60min (customizable)
|
|
||||||
- Instant watched state/resume status sync: This is a 2-way synchronisation. Any watched state or resume status will be instantly (within seconds) reflected to or from Kodi and the server
|
|
||||||
- [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 Companion](https://support.plex.tv/hc/en-us/sections/200276908-Plex-Companion): fling Plex media (or anything else) from other Plex devices to PlexKodiConnect
|
||||||
- Play directly from network paths (e.g. "\\\\server\\Plex\\movie.mkv" or "smb://server/Plex/movie.mkv") instead of 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)
|
|
||||||
- [Plex Transcoding](https://support.plex.tv/hc/en-us/articles/200250377-Transcoding-Media)
|
- [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)
|
||||||
|
|
||||||
|
|
||||||
**Known Issues:**
|
### Known Larger Issues
|
||||||
|
|
||||||
Solutions are unlikely due to the nature of these issues
|
Solutions are unlikely due to the nature of these issues
|
||||||
- *Plex Music when using Addon paths instead of Native Direct Paths:* Kodi tries to scan every(!) single Plex song on startup. This leads to errors in the Kodi log file and potentially even crashes. (Plex puts each song in a "dedicated folder", e.g. 'http://192.168.1.1:32400/library/parts/749450/'. Kodi unsuccessfully tries to scan these folders)
|
- *Plex Music when using Addon paths instead of Native Direct Paths:* Kodi tries to scan every(!) single Plex song on startup. This leads to errors in the Kodi log file and potentially even crashes. See the [Github issue](https://github.com/croneter/PlexKodiConnect/issues/14) for more details
|
||||||
- *Plex Music when using Addon paths instead of Native Direct Paths:* You must have a static IP address for your Plex media server if you plan to use Plex Music features
|
- *Plex Music when using Addon paths instead of Native Direct Paths:* You must have a static IP address for your Plex media server if you plan to use Plex Music features
|
||||||
- If something on the PMS has changed, this change is synced to Kodi. Hence if you rescan your entire library, a long PlexKodiConnect re-sync is triggered
|
- If something on the PMS has changed, this change is synced to Kodi. Hence if you rescan your entire library, a long PlexKodiConnect re-sync is triggered. You can [change your PMS settings to avoid that](https://github.com/croneter/PlexKodiConnect/wiki/Configure-PKC-on-the-First-Run#deactivate-frequent-updates)
|
||||||
- External Plex subtitles (in separate files, e.g. mymovie.srt) can be used, but it is impossible to label them correctly/tell what language they are in. However, this is not the case if you use direct paths
|
- External Plex subtitles (in separate files, e.g. mymovie.srt) can be used, but it is impossible to label them correctly and tell what language they are in. However, this is not the case if you use direct paths
|
||||||
- If using Addon Paths: In the TV show video nodes On Deck and Recently Added, Kodi will not display the Episode Information screen if you push "i". This is a Kodi issue. It does work if you use Direct Paths
|
|
||||||
|
|
||||||
*Background Sync:*
|
*Background Sync:*
|
||||||
The Plex Server does not tell anyone of the following changes. Hence PKC cannot detect these changes instantly but will notice them on full/delta syncs.
|
The Plex Server does not tell anyone of the following changes. Hence PKC cannot detect these changes instantly but will notice them only on full/delta syncs (standard settings is every 60 minutes)
|
||||||
- Toggle the viewstate of an item to (un)watched outside of Kodi
|
- Toggle the viewstate of an item to (un)watched outside of Kodi
|
||||||
- Changing details of an item, e.g. replacing a poster
|
- Changing details of an item, e.g. replacing a poster
|
||||||
|
|
||||||
However, some changes to individual items are instantly detected, e.g. if you match a yet unrecognized movie.
|
However, some changes to individual items are instantly detected, e.g. if you match a yet unrecognized movie.
|
||||||
|
|
||||||
|
|
||||||
**Known Bugs:**
|
### Issues being worked on
|
||||||
- Resume a video does not work yet with Plex Watch Later
|
|
||||||
|
Have a look at the [Github Issues Page](https://github.com/croneter/PlexKodiConnect/issues).
|
||||||
|
|
||||||
|
|
||||||
**What could be in the pipeline for future development?**
|
### What could be in the pipeline for future development?
|
||||||
|
|
||||||
- Playlists
|
- Playlists
|
||||||
- Pictures
|
|
||||||
- Music Videos
|
- Music Videos
|
||||||
- Automatic updates
|
- Deleting PMS items from Kodi
|
||||||
- Simultaneously connecting to several PMS
|
|
||||||
- TV Shows Theme Music (ultra-low prio)
|
- TV Shows Theme Music (ultra-low prio)
|
||||||
|
|
||||||
|
|
||||||
**Important note about MySQL database in kodi**
|
### Important note about MySQL database in Kodi
|
||||||
|
|
||||||
The addon is not (and will not be) compatible with the MySQL database replacement in Kodi. In fact, PlexKodiConnect takes over the point of having a MySQL database because it acts as a "man in the middle" for your entire media library.
|
The addon is not (and will not be) compatible with the MySQL database replacement in Kodi. In fact, PlexKodiConnect takes over the point of having a MySQL database because it acts as a "man in the middle" for your entire media library.
|
||||||
|
|
||||||
**Important note about user collections/nodes**
|
|
||||||
|
|
||||||
Plex has the ability to create custom libraries for your Media, such as having a separate folder for your "Kids Movies" etc. In Kodi this isn't supported, you just have "movies" or "tvshows". But... Kodi let's you create your own playlists and tags to get this same experience. During the sync the foldernode from the Plex server is added to the movies that are imported. In Kodi you can browse to Movie library --> tags and you will have a filtered result that points at your custom node. If you have a skin that let's you create any kind of shortcut on the homescreen you can simply create a shortcut to that tag. Another possibility is to create a "smart playlist" with Kodi and set it to show the content of a certain tag.
|
### Credits
|
||||||
|
|
||||||
**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!!
|
- 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!!
|
||||||
- Plex Companion ("PlexBMC Helper") and other stuff was adapted from @Hippojay 's great work (see https://github.com/hippojay).
|
- Plex Companion ("PlexBMC Helper") and other stuff was adapted from @Hippojay 's great work (see https://github.com/hippojay).
|
||||||
- The foundation of the Plex API is all iBaa's work (https://github.com/iBaa/PlexConnect).
|
- The foundation of the Plex API is all iBaa's work (https://github.com/iBaa/PlexConnect).
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="plugin.video.plexkodiconnect"
|
<addon id="plugin.video.plexkodiconnect"
|
||||||
name="PlexKodiConnect"
|
name="PlexKodiConnect"
|
||||||
version="1.1.4"
|
version="1.3.0"
|
||||||
provider-name="croneter">
|
provider-name="croneter">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.1.0"/>
|
<import addon="xbmc.python" version="2.1.0"/>
|
||||||
|
|
136
changelog.txt
136
changelog.txt
|
@ -1,3 +1,139 @@
|
||||||
|
version 1.3.0
|
||||||
|
- Compatibility with latest Kodi Krypton
|
||||||
|
- Complete redesign of Plex Companion playlist handling
|
||||||
|
- Improvements to Plex Companion daemon
|
||||||
|
- Try reducing strain on PMS for metadata
|
||||||
|
- Don't let PMS crash: download one item at a time
|
||||||
|
- Don't open PKC settings if PMS not found
|
||||||
|
- sync: dont force show "full library sync finished" (thanks @milaq)
|
||||||
|
- fix 'raspberry pi' spelling and capitalisation (thanks @milaq)
|
||||||
|
- Revert: New setting to lower the number of PMS items to cache
|
||||||
|
|
||||||
|
version 1.2.14 (beta only)
|
||||||
|
- New setting to lower the number of PMS items to cache. Hopefully fixes Wetek crashes
|
||||||
|
|
||||||
|
version 1.2.13 (beta only)
|
||||||
|
- Compatibility with latest Kodi Krypton
|
||||||
|
- fix 'raspberry pi' spelling and capitalisation (thanks @milaq)
|
||||||
|
|
||||||
|
version 1.2.12 (beta only)
|
||||||
|
- Complete redesign of playlist handling
|
||||||
|
- Improvements to Plex Companion daemon
|
||||||
|
- Try reducing strain on PMS for metadata
|
||||||
|
- Don't let PMS crash: download one item at a time
|
||||||
|
- sync: dont force show "full library sync finished" (thanks @milaq)
|
||||||
|
|
||||||
|
version 1.2.11
|
||||||
|
- Fix PKC not releasing connections to the PMS. Should fix memory, connection and PMS issues
|
||||||
|
- Fix TypeError for playlists
|
||||||
|
|
||||||
|
version 1.2.10
|
||||||
|
- Hotfix: Fix ValueError for playing certain files
|
||||||
|
|
||||||
|
version 1.2.9
|
||||||
|
- Don't let the PMS force scan the media when requesting PMS item metadata
|
||||||
|
- Improve detection of the need to transcode
|
||||||
|
- Increase (and enforce) a higher connection timeout
|
||||||
|
- Enable stream/media selection for direct play (e.g. if you have several files for the same movie item on the PMS)
|
||||||
|
|
||||||
|
version 1.2.8
|
||||||
|
- Fix PKC playstate updates for widgets on Krypton
|
||||||
|
- Let user choose to always play trailer in highest quality
|
||||||
|
- Fixes to choice of media stream
|
||||||
|
- Plex Companion: fix skipping forward and backward
|
||||||
|
|
||||||
|
version 1.2.7
|
||||||
|
- Let the user pick between several streams or if you have, several different files for the same movie (can be deactivated in the settings)
|
||||||
|
- Use the playing item's filename and path to figure out the Plex id. Now Plex should really always be informed what you're currently playing
|
||||||
|
|
||||||
|
version 1.2.6
|
||||||
|
- Fix Watch Later TypeError
|
||||||
|
|
||||||
|
version 1.2.5
|
||||||
|
- Plex Photos! Choose "Refresh Plex playlists/nodes" to use the new feature.
|
||||||
|
- Compatibility with latest Kodi Krypton (which is still under heavy development and in an alpha state). If PKC stops working for you, update your Kodi Krypton to the latest version.
|
||||||
|
- Fixes to getExtraFanart. If not using the PKC repository, you will have to manually update plugin.video.plexkodiconnect.movies and plugin.video.plexkodiconnect.tvshows to profit from these changes.
|
||||||
|
- Use language codes ('spa'), not verbose 'español' for audio streams and subtitles. You will have to reset your Kodi DB manually to profit from this change.
|
||||||
|
- Fix fanart.tv fallback to English not working.
|
||||||
|
- Fix plex.tv Watch Later ignored resume points.
|
||||||
|
- Fix double PKC settings strings.
|
||||||
|
|
||||||
|
version 1.2.4
|
||||||
|
- Automatically download Plex collection artwork from FanArtTv! Many thanks to @im85288
|
||||||
|
- A dedicated PKC setting to download this set fanart (independent of the other FanArtTv download)
|
||||||
|
|
||||||
|
version 1.2.3
|
||||||
|
- Improvements to resume points. PKC should now correctly mark an item as completely watched.
|
||||||
|
- Get rid of obsolete setting markPlayed. Mark a video item as played after 90%, just like Plex.
|
||||||
|
|
||||||
|
version 1.2.2
|
||||||
|
- Fix filename change recognition for episodes and movies - finally! If you experienced this, you will have to manually reset the Kodi database in the PKC settings
|
||||||
|
- Fix PKC resume points set way too high
|
||||||
|
- Clarify that transcode settings are TARGET quality
|
||||||
|
|
||||||
|
version 1.2.1
|
||||||
|
- Fix crash when Kodi not playing as expected
|
||||||
|
- Improve player.py stability
|
||||||
|
- Background sync: don't try to process infinitely
|
||||||
|
- Only tell PMS we're connected to what we're playing. This should enable the Plex Media Server to fall to sleep because PKC is not constantly bugging it
|
||||||
|
- Ensure credentials are known when reconnecting
|
||||||
|
- Remove some emby references
|
||||||
|
- Ask on first run if we have a low powered device
|
||||||
|
|
||||||
|
version 1.2.0
|
||||||
|
- Re-wired connection manager completely
|
||||||
|
- Periodically check if PMS address has changed
|
||||||
|
- Smarter, faster way to tell that PMS went offline
|
||||||
|
- Fix DTS-HD audio is not correctly identified (you will need to manually reset your Kodi DB)
|
||||||
|
- Improvements to PMS connection checks
|
||||||
|
- Fix default transcoding quality / network speed (so that PKC won't transcode initially)
|
||||||
|
- Fix direct path replacing possibly several times
|
||||||
|
- Initialize Kodi DBs only once
|
||||||
|
- Correctly update views on server switch
|
||||||
|
|
||||||
|
version 1.1.11
|
||||||
|
- Episodes and movies should now correctly be marked as watched in Kodi
|
||||||
|
|
||||||
|
version 1.1.10
|
||||||
|
- A donation link is up. Your support is much appreciated :-)
|
||||||
|
- Movie sets are working (without set art as this is missing from Plex). Many thanks to mattsch!
|
||||||
|
- Fix playback report and marking an item played. Should fix issues with e.g. episodes not correctly being set to watched in Kodi in the On Deck view
|
||||||
|
- Fix UnicodeEncodeError for file paths
|
||||||
|
- New setting: show watched movies in recently added
|
||||||
|
- New setting: don't show already watched episodes
|
||||||
|
- New setting: Force transcode HEVC
|
||||||
|
- New setting: Force transcode 10bit
|
||||||
|
- New setting: do a sync after screensaver deactivated. Very useful for Kodi for Android as Android may put Kodi in a weird kind of sleep
|
||||||
|
- Merge with plugin.video.emby up to 417b8d3b2237f982d1eab462c130e8a4f445dd8b
|
||||||
|
|
||||||
|
version 1.1.9
|
||||||
|
- Fix new episodes not being detected
|
||||||
|
- Use direct file paths for on deck shows if enabled
|
||||||
|
- Added Python requests to the PKC repo (should fix install problems)
|
||||||
|
|
||||||
|
version 1.1.8
|
||||||
|
- Account for string.encode() not allowing args. This will hopefully fix any sync problems now, especially for Android TV
|
||||||
|
- Also show already watched recently added episodes
|
||||||
|
- Increase logging for background updates
|
||||||
|
|
||||||
|
version 1.1.7
|
||||||
|
- Fix UnicodeDecodeError with file paths on sync
|
||||||
|
- Remove Emby reference from logs
|
||||||
|
|
||||||
|
version 1.1.6
|
||||||
|
- Corrected the addon's folder name from `PlexKodiConnect-develop` to `plugin.video.plexkodiconnect` in the Kodi addon folder. If you still experience issues, check your addon folder and delete `PlexKodiConnect-develop`
|
||||||
|
- Fix TypeError during Plex user switch
|
||||||
|
- Fix TypeError when trying to transcode
|
||||||
|
|
||||||
|
version 1.1.5
|
||||||
|
(you will need to rescan your library)
|
||||||
|
- A Kodi repository for easy installation and updating is now available. Download the [ZIP file from Github](https://github.com/croneter/PlexKodiConnect). Instructions can be found [in the Wiki](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
|
||||||
|
- Additional artwork download from FanartTV! Enable it in the PKC settings under Sync Options
|
||||||
|
- New setting: Add TV show name and SxxExx to an episode in Recently Added
|
||||||
|
- Fix UnicodeEncodeError during sync
|
||||||
|
- Plex Companion now always reports playstate. Should increase stability
|
||||||
|
- Merge Emby for Kodi commits up to 3dbdab79a9d213aab3cb6347af0b8fb905bb6e45
|
||||||
|
|
||||||
version 1.1.4
|
version 1.1.4
|
||||||
(you will need to rescan your library)
|
(you will need to rescan your library)
|
||||||
- Plex Watch Later available as a separate Video Node!
|
- Plex Watch Later available as a separate Video Node!
|
||||||
|
|
|
@ -10,13 +10,26 @@ import xbmc
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
|
|
||||||
addon_ = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
|
||||||
addon_path = addon_.getAddonInfo('path').decode('utf-8')
|
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||||
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
try:
|
||||||
|
addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
||||||
|
except TypeError:
|
||||||
|
addon_path = _addon.getAddonInfo('path').decode()
|
||||||
|
try:
|
||||||
|
base_resource = xbmc.translatePath(os.path.join(
|
||||||
|
addon_path,
|
||||||
|
'resources',
|
||||||
|
'lib')).decode('utf-8')
|
||||||
|
except TypeError:
|
||||||
|
base_resource = xbmc.translatePath(os.path.join(
|
||||||
|
addon_path,
|
||||||
|
'resources',
|
||||||
|
'lib')).decode()
|
||||||
sys.path.append(base_resource)
|
sys.path.append(base_resource)
|
||||||
|
|
||||||
import artwork
|
|
||||||
import utils
|
import utils
|
||||||
|
import artwork
|
||||||
import clientinfo
|
import clientinfo
|
||||||
import downloadutils
|
import downloadutils
|
||||||
import librarysync
|
import librarysync
|
||||||
|
@ -36,31 +49,31 @@ def logMsg(msg, lvl=1):
|
||||||
#Kodi contextmenu item to configure the emby settings
|
#Kodi contextmenu item to configure the emby settings
|
||||||
#for now used to set ratings but can later be used to sync individual items etc.
|
#for now used to set ratings but can later be used to sync individual items etc.
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
itemid = xbmc.getInfoLabel("ListItem.DBID").decode("utf-8")
|
itemid = utils.tryDecode(xbmc.getInfoLabel("ListItem.DBID"))
|
||||||
itemtype = xbmc.getInfoLabel("ListItem.DBTYPE").decode("utf-8")
|
itemtype = utils.tryDecode(xbmc.getInfoLabel("ListItem.DBTYPE"))
|
||||||
|
|
||||||
emby = embyserver.Read_EmbyServer()
|
emby = embyserver.Read_EmbyServer()
|
||||||
|
|
||||||
embyid = ""
|
plexid = ""
|
||||||
if not itemtype and xbmc.getCondVisibility("Container.Content(albums)"): itemtype = "album"
|
if not itemtype and xbmc.getCondVisibility("Container.Content(albums)"): itemtype = "album"
|
||||||
if not itemtype and xbmc.getCondVisibility("Container.Content(artists)"): itemtype = "artist"
|
if not itemtype and xbmc.getCondVisibility("Container.Content(artists)"): itemtype = "artist"
|
||||||
if not itemtype and xbmc.getCondVisibility("Container.Content(songs)"): itemtype = "song"
|
if not itemtype and xbmc.getCondVisibility("Container.Content(songs)"): itemtype = "song"
|
||||||
if not itemtype and xbmc.getCondVisibility("Container.Content(pictures)"): itemtype = "picture"
|
if not itemtype and xbmc.getCondVisibility("Container.Content(pictures)"): itemtype = "picture"
|
||||||
|
|
||||||
if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"):
|
if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(plexid)"):
|
||||||
embyid = xbmc.getInfoLabel("ListItem.Property(embyid)")
|
plexid = xbmc.getInfoLabel("ListItem.Property(plexid)")
|
||||||
else:
|
else:
|
||||||
with embydb.GetEmbyDB() as emby_db:
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
item = emby_db.getItem_byKodiId(itemid, itemtype)
|
item = emby_db.getItem_byKodiId(itemid, itemtype)
|
||||||
if item:
|
if item:
|
||||||
embyid = item[0]
|
plexid = item[0]
|
||||||
|
|
||||||
logMsg("Contextmenu opened for embyid: %s - itemtype: %s" %(embyid,itemtype))
|
logMsg("Contextmenu opened for plexid: %s - itemtype: %s" %(plexid,itemtype))
|
||||||
|
|
||||||
if embyid:
|
if plexid:
|
||||||
item = PF.GetPlexMetadata(embyid)
|
item = PF.GetPlexMetadata(plexid)
|
||||||
if item is None or item == 401:
|
if item is None or item == 401:
|
||||||
logMsg('Could not get item metadata for item %s' % embyid, -1)
|
logMsg('Could not get item metadata for item %s' % plexid, -1)
|
||||||
return
|
return
|
||||||
API = PlexAPI.API(item[0])
|
API = PlexAPI.API(item[0])
|
||||||
userdata = API.getUserData()
|
userdata = API.getUserData()
|
||||||
|
@ -98,15 +111,15 @@ if __name__ == '__main__':
|
||||||
ret = xbmcgui.Dialog().select(header, options)
|
ret = xbmcgui.Dialog().select(header, options)
|
||||||
if ret != -1:
|
if ret != -1:
|
||||||
if options[ret] == utils.language(30402):
|
if options[ret] == utils.language(30402):
|
||||||
emby.updateUserRating(embyid, deletelike=True)
|
emby.updateUserRating(plexid, deletelike=True)
|
||||||
if options[ret] == utils.language(30403):
|
if options[ret] == utils.language(30403):
|
||||||
emby.updateUserRating(embyid, like=True)
|
emby.updateUserRating(plexid, like=True)
|
||||||
if options[ret] == utils.language(30404):
|
if options[ret] == utils.language(30404):
|
||||||
emby.updateUserRating(embyid, like=False)
|
emby.updateUserRating(plexid, like=False)
|
||||||
if options[ret] == utils.language(30405):
|
if options[ret] == utils.language(30405):
|
||||||
emby.updateUserRating(embyid, favourite=True)
|
emby.updateUserRating(plexid, favourite=True)
|
||||||
if options[ret] == utils.language(30406):
|
if options[ret] == utils.language(30406):
|
||||||
emby.updateUserRating(embyid, favourite=False)
|
emby.updateUserRating(plexid, favourite=False)
|
||||||
if options[ret] == utils.language(30407):
|
if options[ret] == utils.language(30407):
|
||||||
kodiconn = utils.kodiSQL('music')
|
kodiconn = utils.kodiSQL('music')
|
||||||
kodicursor = kodiconn.cursor()
|
kodicursor = kodiconn.cursor()
|
||||||
|
@ -121,7 +134,7 @@ if __name__ == '__main__':
|
||||||
musicutils.updateRatingToFile(newvalue, API.getFilePath())
|
musicutils.updateRatingToFile(newvalue, API.getFilePath())
|
||||||
if utils.settings('enableExportSongRating') == "true":
|
if utils.settings('enableExportSongRating') == "true":
|
||||||
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
|
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
|
||||||
emby.updateUserRating(embyid, like, favourite, deletelike)
|
emby.updateUserRating(plexid, like, favourite, deletelike)
|
||||||
query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
|
query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
|
||||||
kodicursor.execute(query, (newvalue,itemid,))
|
kodicursor.execute(query, (newvalue,itemid,))
|
||||||
kodiconn.commit()
|
kodiconn.commit()
|
||||||
|
@ -139,15 +152,15 @@ if __name__ == '__main__':
|
||||||
line1=("Delete file from Emby Server? This will "
|
line1=("Delete file from Emby Server? This will "
|
||||||
"also delete the file(s) from disk!"))
|
"also delete the file(s) from disk!"))
|
||||||
if not resp:
|
if not resp:
|
||||||
logMsg("User skipped deletion for: %s." % embyid, 1)
|
logMsg("User skipped deletion for: %s." % plexid, 1)
|
||||||
delete = False
|
delete = False
|
||||||
|
|
||||||
if delete:
|
if delete:
|
||||||
import downloadutils
|
import downloadutils
|
||||||
doUtils = downloadutils.DownloadUtils()
|
doUtils = downloadutils.DownloadUtils()
|
||||||
url = "{server}/emby/Items/%s?format=json" % embyid
|
url = "{server}/emby/Items/%s?format=json" % plexid
|
||||||
logMsg("Deleting request: %s" % embyid, 0)
|
logMsg("Deleting request: %s" % plexid, 0)
|
||||||
doUtils.downloadUrl(url, type="DELETE")
|
doUtils.downloadUrl(url, action_type="DELETE")
|
||||||
|
|
||||||
'''if utils.settings('skipContextMenu') != "true":
|
'''if utils.settings('skipContextMenu') != "true":
|
||||||
if xbmcgui.Dialog().yesno(
|
if xbmcgui.Dialog().yesno(
|
||||||
|
@ -156,8 +169,7 @@ if __name__ == '__main__':
|
||||||
"also delete the file(s) from disk!")):
|
"also delete the file(s) from disk!")):
|
||||||
import downloadutils
|
import downloadutils
|
||||||
doUtils = downloadutils.DownloadUtils()
|
doUtils = downloadutils.DownloadUtils()
|
||||||
url = "{server}/emby/Items/%s?format=json" % embyid
|
doUtils.downloadUrl("{server}/emby/Items/%s?format=json" % plexid, action_type="DELETE")'''
|
||||||
doUtils.downloadUrl(url, type="DELETE")'''
|
|
||||||
|
|
||||||
xbmc.sleep(500)
|
xbmc.sleep(500)
|
||||||
xbmc.executebuiltin("Container.Update")
|
xbmc.executebuiltin("Container.Update")
|
50
default.py
50
default.py
|
@ -10,11 +10,24 @@ import xbmc
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
addon_ = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||||
addon_path = addon_.getAddonInfo('path').decode('utf-8')
|
try:
|
||||||
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
||||||
|
except TypeError:
|
||||||
|
addon_path = _addon.getAddonInfo('path').decode()
|
||||||
|
try:
|
||||||
|
base_resource = xbmc.translatePath(os.path.join(
|
||||||
|
addon_path,
|
||||||
|
'resources',
|
||||||
|
'lib')).decode('utf-8')
|
||||||
|
except TypeError:
|
||||||
|
base_resource = xbmc.translatePath(os.path.join(
|
||||||
|
addon_path,
|
||||||
|
'resources',
|
||||||
|
'lib')).decode()
|
||||||
sys.path.append(base_resource)
|
sys.path.append(base_resource)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -35,7 +48,6 @@ class Main:
|
||||||
xbmc.log("PlexKodiConnect - Full sys.argv received: %s" % sys.argv)
|
xbmc.log("PlexKodiConnect - Full sys.argv received: %s" % sys.argv)
|
||||||
base_url = sys.argv[0]
|
base_url = sys.argv[0]
|
||||||
params = urlparse.parse_qs(sys.argv[2][1:])
|
params = urlparse.parse_qs(sys.argv[2][1:])
|
||||||
xbmc.log("PlexKodiConnect - Parameter string: %s" % sys.argv[2])
|
|
||||||
try:
|
try:
|
||||||
mode = params['mode'][0]
|
mode = params['mode'][0]
|
||||||
itemid = params.get('id', '')
|
itemid = params.get('id', '')
|
||||||
|
@ -67,18 +79,20 @@ class Main:
|
||||||
'companion': entrypoint.plexCompanion,
|
'companion': entrypoint.plexCompanion,
|
||||||
'switchuser': entrypoint.switchPlexUser,
|
'switchuser': entrypoint.switchPlexUser,
|
||||||
'deviceid': entrypoint.resetDeviceId,
|
'deviceid': entrypoint.resetDeviceId,
|
||||||
'reConnect': entrypoint.reConnect,
|
|
||||||
'delete': entrypoint.deleteItem,
|
'delete': entrypoint.deleteItem,
|
||||||
'browseplex': entrypoint.BrowsePlexContent,
|
'browseplex': entrypoint.BrowsePlexContent,
|
||||||
'ondeck': entrypoint.getOnDeck,
|
'ondeck': entrypoint.getOnDeck,
|
||||||
'chooseServer': entrypoint.chooseServer,
|
'chooseServer': entrypoint.chooseServer,
|
||||||
'watchlater': entrypoint.watchlater
|
'watchlater': entrypoint.watchlater,
|
||||||
|
'enterPMS': entrypoint.enterPMS,
|
||||||
|
'togglePlexTV': entrypoint.togglePlexTV,
|
||||||
|
'playwatchlater': entrypoint.playWatchLater
|
||||||
}
|
}
|
||||||
|
|
||||||
if "/extrafanart" in sys.argv[0]:
|
if "/extrafanart" in sys.argv[0]:
|
||||||
embypath = sys.argv[2][1:]
|
plexpath = sys.argv[2][1:]
|
||||||
embyid = params.get('id',[""])[0]
|
plexid = params.get('id', [""])[0]
|
||||||
entrypoint.getExtraFanArt(embyid,embypath)
|
entrypoint.getExtraFanArt(plexid, plexpath)
|
||||||
|
|
||||||
# Called by e.g. 3rd party plugin video extras
|
# Called by e.g. 3rd party plugin video extras
|
||||||
if ("/Extras" in sys.argv[0] or "/VideoFiles" in sys.argv[0] or
|
if ("/Extras" in sys.argv[0] or "/VideoFiles" in sys.argv[0] or
|
||||||
|
@ -121,6 +135,8 @@ class Main:
|
||||||
modes[mode](itemid, folderid)
|
modes[mode](itemid, folderid)
|
||||||
elif mode == "companion":
|
elif mode == "companion":
|
||||||
modes[mode](itemid, params=sys.argv[2])
|
modes[mode](itemid, params=sys.argv[2])
|
||||||
|
elif mode == 'playwatchlater':
|
||||||
|
modes[mode](params.get('id')[0], params.get('viewOffset')[0])
|
||||||
else:
|
else:
|
||||||
modes[mode]()
|
modes[mode]()
|
||||||
else:
|
else:
|
||||||
|
@ -128,12 +144,14 @@ class Main:
|
||||||
if mode == "settings":
|
if mode == "settings":
|
||||||
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||||
elif mode in ("manualsync", "repair"):
|
elif mode in ("manualsync", "repair"):
|
||||||
if utils.window('emby_online') != "true":
|
if utils.window('plex_online') != "true":
|
||||||
# Server is not online, do not run the sync
|
# Server is not online, do not run the sync
|
||||||
xbmcgui.Dialog().ok(heading="PlexKodiConnect",
|
xbmcgui.Dialog().ok(
|
||||||
line1=("Unable to run the sync, the add-on is not "
|
"PlexKodiConnect",
|
||||||
"connected to the Emby server."))
|
"Unable to run the sync, the add-on is not connected "
|
||||||
utils.logMsg("PLEX", "Not connected to the emby server.", 1)
|
"to a Plex server.")
|
||||||
|
utils.logMsg("PLEX",
|
||||||
|
"Not connected to a PMS.", -1)
|
||||||
return
|
return
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -159,8 +177,8 @@ if ( __name__ == "__main__" ):
|
||||||
import pstats
|
import pstats
|
||||||
import random
|
import random
|
||||||
from time import gmtime, strftime
|
from time import gmtime, strftime
|
||||||
addonid = addon_.getAddonInfo('id').decode( 'utf-8' )
|
addonid = utils.tryDecode(addon_.getAddonInfo('id'))
|
||||||
datapath = os.path.join( xbmc.translatePath( "special://profile/" ).decode( 'utf-8' ), "addon_data", addonid )
|
datapath = os.path.join(utils.tryDecode(xbmc.translatePath( "special://profile/" )), "addon_data", addonid )
|
||||||
|
|
||||||
filename = os.path.join( datapath, strftime( "%Y%m%d%H%M%S",gmtime() ) + "-" + str( random.randrange(0,100000) ) + ".log" )
|
filename = os.path.join( datapath, strftime( "%Y%m%d%H%M%S",gmtime() ) + "-" + str( random.randrange(0,100000) ) + ".log" )
|
||||||
cProfile.run( 'Main()', filename )
|
cProfile.run( 'Main()', filename )
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<strings>
|
<strings>
|
||||||
<!-- Add-on settings -->
|
<!-- Add-on settings -->
|
||||||
<string id="30000">Server Address (IP)</string><!-- Verified -->
|
<string id="30000">Server Address (IP)</string><!-- Verified -->
|
||||||
<string id="30002">Prefered playback method</string><!-- Verified -->
|
<string id="30002">Preferred playback method</string><!-- Verified -->
|
||||||
<string id="30004">Log level</string><!-- Verified -->
|
<string id="30004">Log level</string><!-- Verified -->
|
||||||
<string id="30005">Username: </string>
|
<string id="30005">Username: </string>
|
||||||
<string id="30006">Password: </string>
|
<string id="30006">Password: </string>
|
||||||
|
@ -131,7 +131,7 @@
|
||||||
<string id="30157">Enable Enhanced Images (eg CoverArt)</string><!-- Verified -->
|
<string id="30157">Enable Enhanced Images (eg CoverArt)</string><!-- Verified -->
|
||||||
<string id="30158">Metadata</string>
|
<string id="30158">Metadata</string>
|
||||||
<string id="30159">Artwork</string>
|
<string id="30159">Artwork</string>
|
||||||
<string id="30160">Video Quality for Transcoding</string><!-- Verified -->
|
<string id="30160">Video Quality if Transcoding necessary</string><!-- Verified -->
|
||||||
|
|
||||||
<string id="30161">Enable Suggested Loader (Requires Restart)</string>
|
<string id="30161">Enable Suggested Loader (Requires Restart)</string>
|
||||||
<string id="30162">Add Season Number</string>
|
<string id="30162">Add Season Number</string>
|
||||||
|
@ -302,6 +302,10 @@
|
||||||
<string id="30536">Users must log in every time Kodi restarts</string>
|
<string id="30536">Users must log in every time Kodi restarts</string>
|
||||||
<string id="30537">RESTART KODI IF YOU MAKE ANY CHANGES</string>
|
<string id="30537">RESTART KODI IF YOU MAKE ANY CHANGES</string>
|
||||||
<string id="30538">Complete Re-Sync necessary</string>
|
<string id="30538">Complete Re-Sync necessary</string>
|
||||||
|
<string id="30539">Download additional art from FanArtTV (slower!)</string>
|
||||||
|
<string id="30540">Download movie set/collection art from FanArtTV</string>
|
||||||
|
<string id="30541">Don't ask to pick a certain stream/quality</string>
|
||||||
|
<string id="30542">Always pick best quality for trailers</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- service add-on -->
|
<!-- service add-on -->
|
||||||
|
@ -328,7 +332,7 @@
|
||||||
<string id="33020">Gathering tv shows from:</string>
|
<string id="33020">Gathering tv shows from:</string>
|
||||||
<string id="33021">Gathering:</string>
|
<string id="33021">Gathering:</string>
|
||||||
<string id="33022">Detected the database needs to be recreated for this version of Emby for Kodi. Proceed?</string>
|
<string id="33022">Detected the database needs to be recreated for this version of Emby for Kodi. Proceed?</string>
|
||||||
<string id="33023">Emby for Kod may not work correctly until the database is reset.</string>
|
<string id="33023">Emby for Kodi may not work correctly until the database is reset.</string>
|
||||||
<string id="33024">Cancelling the database syncing process. The current Kodi version is unsupported.</string>
|
<string id="33024">Cancelling the database syncing process. The current Kodi version is unsupported.</string>
|
||||||
<string id="33025">completed in:</string>
|
<string id="33025">completed in:</string>
|
||||||
<string id="33026">Comparing movies from:</string>
|
<string id="33026">Comparing movies from:</string>
|
||||||
|
@ -338,7 +342,7 @@
|
||||||
<string id="33030">Comparing episodes from:</string>
|
<string id="33030">Comparing episodes from:</string>
|
||||||
<string id="33031">Comparing:</string>
|
<string id="33031">Comparing:</string>
|
||||||
<string id="33032">Failed to generate a new device Id. See your logs for more information.</string>
|
<string id="33032">Failed to generate a new device Id. See your logs for more information.</string>
|
||||||
<string id="33033">A new device Id has been generated. Kodi will now restart.</string>
|
<string id="33033">Kodi will now restart to apply the changes.</string>
|
||||||
|
|
||||||
<!-- New to Plex -->
|
<!-- New to Plex -->
|
||||||
<string id="39000">- Number of trailers to play before a movie</string>
|
<string id="39000">- Number of trailers to play before a movie</string>
|
||||||
|
@ -366,7 +370,8 @@
|
||||||
<string id="39021">[COLOR yellow]Sync Emby Theme Media to Kodi[/COLOR]</string>
|
<string id="39021">[COLOR yellow]Sync Emby Theme Media to Kodi[/COLOR]</string>
|
||||||
<string id="39022">local</string>
|
<string id="39022">local</string>
|
||||||
<string id="39023">Failed to authenticate. Did you login to plex.tv?</string>
|
<string id="39023">Failed to authenticate. Did you login to plex.tv?</string>
|
||||||
<string id="39024">[COLOR yellow]Reset PMS and plex.tv connections to re-login[/COLOR]</string>
|
|
||||||
|
|
||||||
<string id="39025">Automatically log into plex.tv on startup</string>
|
<string id="39025">Automatically log into plex.tv on startup</string>
|
||||||
<string id="39026">Enable constant background sync</string>
|
<string id="39026">Enable constant background sync</string>
|
||||||
<string id="39027">Playback Mode</string>
|
<string id="39027">Playback Mode</string>
|
||||||
|
@ -387,11 +392,10 @@
|
||||||
<string id="39042">Replace Plex MUSIC with:</string>
|
<string id="39042">Replace Plex MUSIC with:</string>
|
||||||
<string id="39043">Go a step further and completely replace all original Plex library paths (/volume1/media) with custom SMB paths (smb://NAS/MyStuff)?</string>
|
<string id="39043">Go a step further and completely replace all original Plex library paths (/volume1/media) with custom SMB paths (smb://NAS/MyStuff)?</string>
|
||||||
<string id="39044">Please enter your custom smb paths in the settings under "Sync Options" and then restart Kodi</string>
|
<string id="39044">Please enter your custom smb paths in the settings under "Sync Options" and then restart Kodi</string>
|
||||||
|
<string id="39045">Original Plex PHOTO path to replace:</string>
|
||||||
<string id="39045">Appearance Tweaks</string>
|
<string id="39046">Replace Plex PHOTO with:</string>
|
||||||
<string id="39046">TV Shows</string>
|
|
||||||
<string id="39047">On Deck: Append show title to episode</string>
|
<string id="39047">On Deck: Append show title to episode</string>
|
||||||
<string id="39048">On Deck: Append season- and episode-number (e.g. S3E2)</string>
|
<string id="39048">On Deck: Append season- and episode-number SxxExx</string>
|
||||||
<string id="39049">Nothing works? Try a full reset!</string>
|
<string id="39049">Nothing works? Try a full reset!</string>
|
||||||
<string id="39050">[COLOR yellow]Choose Plex Server from a list[/COLOR]</string>
|
<string id="39050">[COLOR yellow]Choose Plex Server from a list[/COLOR]</string>
|
||||||
<string id="39051">Wait before sync new/changed PMS item [s]</string>
|
<string id="39051">Wait before sync new/changed PMS item [s]</string>
|
||||||
|
@ -402,6 +406,22 @@
|
||||||
<string id="39056">Used by Sync and when attempting to Direct Play</string>
|
<string id="39056">Used by Sync and when attempting to Direct Play</string>
|
||||||
<string id="39057">Customize Paths</string>
|
<string id="39057">Customize Paths</string>
|
||||||
<string id="39058">Extend Plex TV Series "On Deck" view to all shows</string>
|
<string id="39058">Extend Plex TV Series "On Deck" view to all shows</string>
|
||||||
|
<string id="39059">Recently Added: Append show title to episode</string>
|
||||||
|
<string id="39060">Recently Added: Append season- and episode-number SxxExx</string>
|
||||||
|
<string id="39061">Would you like to download additional artwork from FanArtTV? Sync will be slower!</string>
|
||||||
|
<string id="39062">Sync when screensaver is deactivated</string>
|
||||||
|
<string id="39063">Force Transcode Hi10P</string>
|
||||||
|
<string id="39064">Recently Added: Also show already watched episodes</string>
|
||||||
|
<string id="39065">Force Transcode HEVC</string>
|
||||||
|
<string id="39066">Recently Added: Also show already watched movies (Refresh Plex playlist/nodes!)</string>
|
||||||
|
<string id="39067">Your current Plex Media Server:</string>
|
||||||
|
<string id="39068">[COLOR yellow]Manually enter Plex Media Server address[/COLOR]</string>
|
||||||
|
<string id="39069">Current address:</string>
|
||||||
|
<string id="39070">Current port:</string>
|
||||||
|
<string id="39071">Current plex.tv status:</string>
|
||||||
|
<string id="39072">Is your Kodi installed on a low-powered device like a Raspberry Pi? If yes, then we will reduce the strain on Kodi to prevent it from crashing.</string>
|
||||||
|
<string id="39073">Appearance Tweaks</string>
|
||||||
|
<string id="39074">TV Shows</string>
|
||||||
|
|
||||||
<!-- Plex Entrypoint.py -->
|
<!-- Plex Entrypoint.py -->
|
||||||
<string id="39200">Log-out Plex Home User </string>
|
<string id="39200">Log-out Plex Home User </string>
|
||||||
|
@ -411,11 +431,20 @@
|
||||||
<string id="39204">Perform manual library sync</string>
|
<string id="39204">Perform manual library sync</string>
|
||||||
<string id="39205">Unable to run the sync, the add-on is not connected to a Plex server.</string>
|
<string id="39205">Unable to run the sync, the add-on is not connected to a Plex server.</string>
|
||||||
<string id="39206">Plex might lock your account if you fail to log in too many times. Proceed anyway?</string>
|
<string id="39206">Plex might lock your account if you fail to log in too many times. Proceed anyway?</string>
|
||||||
<string id="39207">Reseting PMS connections, please wait</string>
|
<string id="39207">Resetting PMS connections, please wait</string>
|
||||||
<string id="39208">Failed to reset PMS and plex.tv connects. Try to restart Kodi.</string>
|
<string id="39208">Failed to reset PKC. Try to restart Kodi.</string>
|
||||||
<string id="39209">[COLOR yellow]Log-in to plex.tv[/COLOR]</string>
|
<string id="39209">[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]</string>
|
||||||
<string id="39210">Not yet connected to Plex Server</string>
|
<string id="39210">Not yet connected to Plex Server</string>
|
||||||
<string id="39211">Watch later</string>
|
<string id="39211">Watch later</string>
|
||||||
|
<string id="39213">is offline</string>
|
||||||
|
<string id="39214">Even though we signed in to plex.tv, we could not authorize for PMS</string>
|
||||||
|
<string id="39215">Enter your Plex Media Server's IP or URL, Examples are:</string>
|
||||||
|
|
||||||
|
<string id="39217">Does your Plex Media Server support SSL connections? (https instead of http)?</string>
|
||||||
|
<string id="39218">Error contacting PMS</string>
|
||||||
|
<string id="39219">Abort (Yes) or save address anyway (No)?</string>
|
||||||
|
<string id="39220">connected</string>
|
||||||
|
<string id="39221">plex.tv toggle successful</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Plex Artwork.py -->
|
<!-- Plex Artwork.py -->
|
||||||
|
|
|
@ -28,8 +28,10 @@
|
||||||
<string id="30536">Benutzer müssen sich bei jedem Neustart von Kodi neu anmelden</string>
|
<string id="30536">Benutzer müssen sich bei jedem Neustart von Kodi neu anmelden</string>
|
||||||
<string id="30537">BEI ÄNDERUNGEN KODI NEU STARTEN</string>
|
<string id="30537">BEI ÄNDERUNGEN KODI NEU STARTEN</string>
|
||||||
<string id="30538">Komplette Neusynchronisierung nötig</string>
|
<string id="30538">Komplette Neusynchronisierung nötig</string>
|
||||||
|
<string id="30539">Zusätzliche Bilder von FanArtTV herunterladen (langsamer!)</string>
|
||||||
|
<string id="30540">FanArtTV Film-Sets/Collections Bilder herunterladen</string>
|
||||||
|
<string id="30541">Nicht fragen, welcher Stream/Qualität gespielt wird</string>
|
||||||
|
<string id="30542">Trailer immer in der besten Qualität abspielen</string>
|
||||||
|
|
||||||
<string id="30014">Verbindung</string>
|
<string id="30014">Verbindung</string>
|
||||||
<string id="30015">Netzwerk</string>
|
<string id="30015">Netzwerk</string>
|
||||||
|
@ -164,7 +166,7 @@
|
||||||
<string id="30157">Deaktiviere erweiterte Bilder (z.B. CoverArt)</string>
|
<string id="30157">Deaktiviere erweiterte Bilder (z.B. CoverArt)</string>
|
||||||
<string id="30158">Metadaten</string>
|
<string id="30158">Metadaten</string>
|
||||||
<string id="30159">Grafiken</string>
|
<string id="30159">Grafiken</string>
|
||||||
<string id="30160">Videoqualität für Transkodierung</string>
|
<string id="30160">Videoqualität falls Transkodierung nötig</string>
|
||||||
|
|
||||||
<string id="30161">'Empfohlen'-Loader aktivieren (Erfordert Neustart)</string>
|
<string id="30161">'Empfohlen'-Loader aktivieren (Erfordert Neustart)</string>
|
||||||
<string id="30162">Staffelnummer hinzufügen</string>
|
<string id="30162">Staffelnummer hinzufügen</string>
|
||||||
|
@ -278,6 +280,8 @@
|
||||||
<string id="30311">Musikstücke</string>
|
<string id="30311">Musikstücke</string>
|
||||||
<string id="30312">Kanäle</string>
|
<string id="30312">Kanäle</string>
|
||||||
|
|
||||||
|
<string id="33033">Kodi wird jetzt neu gestartet um die Änderungen anzuwenden.</string>
|
||||||
|
|
||||||
<!-- New to Plex -->
|
<!-- New to Plex -->
|
||||||
<string id="39000">- Anzahl abzuspielender Trailer vor einem Film</string>
|
<string id="39000">- Anzahl abzuspielender Trailer vor einem Film</string>
|
||||||
<string id="39001">Audio Verstärkung (audio boost) wenn transkodiert wird</string>
|
<string id="39001">Audio Verstärkung (audio boost) wenn transkodiert wird</string>
|
||||||
|
@ -304,7 +308,8 @@
|
||||||
<string id="39021">[COLOR yellow]Plex Themes zu Kodi synchronisieren[/COLOR]</string>
|
<string id="39021">[COLOR yellow]Plex Themes zu Kodi synchronisieren[/COLOR]</string>
|
||||||
<string id="39022">lokal</string>
|
<string id="39022">lokal</string>
|
||||||
<string id="39023">Plex Media Server Authentifizierung fehlgeschlagen. Haben Sie sich bei plex.tv eingeloggt?</string>
|
<string id="39023">Plex Media Server Authentifizierung fehlgeschlagen. Haben Sie sich bei plex.tv eingeloggt?</string>
|
||||||
<string id="39024">[COLOR yellow]PMS und plex.tv Verbindungen zurücksetzen für erneuten Login[/COLOR]</string>
|
|
||||||
|
|
||||||
<string id="39025">Automatisch beim Starten bei plex.tv einloggen</string>
|
<string id="39025">Automatisch beim Starten bei plex.tv einloggen</string>
|
||||||
<string id="39026">Laufende Synchronisierung im Hintergrund aktivieren</string>
|
<string id="39026">Laufende Synchronisierung im Hintergrund aktivieren</string>
|
||||||
<string id="39027">Playback Modus</string>
|
<string id="39027">Playback Modus</string>
|
||||||
|
@ -325,11 +330,10 @@
|
||||||
<string id="39042">Plex MUSIK Pfade ersetzen durch:</string>
|
<string id="39042">Plex MUSIK Pfade ersetzen durch:</string>
|
||||||
<string id="39043">Sollen sogar sämtliche Plex Pfade wie /volume1/Hans/medien durch benutzerdefinierte smb Pfade wie smb://NAS/Filme ersetzt werden?</string>
|
<string id="39043">Sollen sogar sämtliche Plex Pfade wie /volume1/Hans/medien durch benutzerdefinierte smb Pfade wie smb://NAS/Filme ersetzt werden?</string>
|
||||||
<string id="39044">Bitte geben Sie Ihre benutzerdefinierten SMB Pfade nun in den Einstellungen unter Sync Optionen ein. Starten Sie dann Kodi neu.</string>
|
<string id="39044">Bitte geben Sie Ihre benutzerdefinierten SMB Pfade nun in den Einstellungen unter Sync Optionen ein. Starten Sie dann Kodi neu.</string>
|
||||||
|
<string id="39045">Ursprünglicher Plex Pfad für PHOTOS:</string>
|
||||||
<string id="39045">Erscheinung</string>
|
<string id="39046">Plex PHOTO Pfade ersetzen durch:</string>
|
||||||
<string id="39046">TV Serien</string>
|
|
||||||
<string id="39047">"Aktuell": Serien- an Episoden-Titel anfügen</string>
|
<string id="39047">"Aktuell": Serien- an Episoden-Titel anfügen</string>
|
||||||
<string id="39048">"Aktuell": Staffel und Episode anfügen (z.B. S3E2)</string>
|
<string id="39048">"Aktuell": Staffel und Episode anfügen, SxxExx</string>
|
||||||
<string id="39049">Nichts funktioniert? Setze mal alles zurück!</string>
|
<string id="39049">Nichts funktioniert? Setze mal alles zurück!</string>
|
||||||
<string id="39050">[COLOR yellow]Plex Server aus Liste auswählen[/COLOR]</string>
|
<string id="39050">[COLOR yellow]Plex Server aus Liste auswählen[/COLOR]</string>
|
||||||
<string id="39051">Warten bevor neue/geänderte PMS Einträge gesynct werden [s]</string>
|
<string id="39051">Warten bevor neue/geänderte PMS Einträge gesynct werden [s]</string>
|
||||||
|
@ -340,8 +344,22 @@
|
||||||
<string id="39056">Verwendet für Synchronisierung sowie beim Versuch, Direct Play zu nutzen</string>
|
<string id="39056">Verwendet für Synchronisierung sowie beim Versuch, Direct Play zu nutzen</string>
|
||||||
<string id="39057">Pfade ändern</string>
|
<string id="39057">Pfade ändern</string>
|
||||||
<string id="39058">Standard Plex Ansicht "Aktuell" auf alle TV Shows erweitern</string>
|
<string id="39058">Standard Plex Ansicht "Aktuell" auf alle TV Shows erweitern</string>
|
||||||
|
<string id="39059">"Zuletzt hinzugefügt": Serien- an Episoden-Titel anfügen</string>
|
||||||
|
<string id="39060">"Zuletzt hinzugefügt": Staffel und Episode anfügen, SxxExx</string>
|
||||||
|
<string id="39061">Zusätzliche Bilder von FanArtTV herunterladen? Die Synchronisierung wird länger dauern!</string>
|
||||||
|
<string id="39062">Sync wenn Bildschirmschoner deaktiviert wird</string>
|
||||||
|
<string id="39063">Hi10p Codec Transkodierung erzwingen</string>
|
||||||
|
<string id="39064">"Zuletzt hinzugefügt": gesehene Folgen anzeigen</string>
|
||||||
|
<string id="39065">HEVC Codec Transkodierung erzwingen</string>
|
||||||
|
<string id="39066">"Zuletzt hinzugefügt": gesehene Filme anzeigen (Plex Playlisten und Nodes zurücksetzen!)</string>
|
||||||
|
<string id="39067">Aktueller Plex Media Server:</string>
|
||||||
|
<string id="39068">[COLOR yellow]Plex Media Server Adresse manuell eingeben[/COLOR]</string>
|
||||||
|
<string id="39069">Aktuelle Adresse:</string>
|
||||||
|
<string id="39070">Aktueller Port:</string>
|
||||||
|
<string id="39071">Aktueller plex.tv Status:</string>
|
||||||
|
<string id="39072">Läuft Kodi auf einem Raspberry Pi oder ähnlichem Gerät mit äusserst wenig Rechenleistung? Falls ja, wird die Rechenlast reduziert, damit Kodi nicht abstürzt.</string>
|
||||||
|
<string id="39073">Tweaks Aussehen</string>
|
||||||
|
<string id="39074">TV Serien</string>
|
||||||
|
|
||||||
<!-- Plex Entrypoint.py -->
|
<!-- Plex Entrypoint.py -->
|
||||||
<string id="39200">Plex Home Benutzer abmelden: </string>
|
<string id="39200">Plex Home Benutzer abmelden: </string>
|
||||||
|
@ -352,10 +370,19 @@
|
||||||
<string id="39205">Plex Bibliothek kann nicht gescannt werden, da keine Verbindung mit einem Plex Server besteht.</string>
|
<string id="39205">Plex Bibliothek kann nicht gescannt werden, da keine Verbindung mit einem Plex Server besteht.</string>
|
||||||
<string id="39206">Plex könnte möglicherweise Ihren Account sperren, wenn Sie zu oft versuchen, sich erfolglos anzumelden. Trotzdem fortfahren?</string>
|
<string id="39206">Plex könnte möglicherweise Ihren Account sperren, wenn Sie zu oft versuchen, sich erfolglos anzumelden. Trotzdem fortfahren?</string>
|
||||||
<string id="39207">PMS Verbindungen werden zurückgesetzt</string>
|
<string id="39207">PMS Verbindungen werden zurückgesetzt</string>
|
||||||
<string id="39208">PMS und plex.tv Verbindungen konnten nicht zurückgesetzt werden. Bitte versuchen Sie, Kodi neu zu starten, um das Problem zu beheben.</string>
|
<string id="39208">PKC konnte nicht zurückgesetzt werden. Bitte versuchen Sie, Kodi neu zu starten, um das Problem zu beheben.</string>
|
||||||
<string id="39209">[COLOR yellow]Bei plex.tv einloggen[/COLOR]</string>
|
<string id="39209">[COLOR yellow]plex.tv Login wechseln (ein- resp. ausloggen)[/COLOR]</string>
|
||||||
<string id="39210">Noch nicht mit Plex Server verbunden</string>
|
<string id="39210">Noch nicht mit Plex Server verbunden</string>
|
||||||
<string id="39211">Später ansehen</string>
|
<string id="39211">Später ansehen</string>
|
||||||
|
<string id="39213">ist offline</string>
|
||||||
|
<string id="39214">Obwohl mit plex.tv verbunden, konnte keine Verbindung hergestellt werden mit</string>
|
||||||
|
<string id="39215">Plex Media Server IP oder URL eingeben. Zum Beispiel:</string>
|
||||||
|
|
||||||
|
<string id="39217">Unterstützt der Plex Media Server sichere SSL Verbindungen (https anstelle von http)?</string>
|
||||||
|
<string id="39218">Error beim Verbinden mit PMS</string>
|
||||||
|
<string id="39219">Abbrechen (Ja) oder PMS Adresse trotzdem speichern (Nein)?</string>
|
||||||
|
<string id="39220">verbunden</string>
|
||||||
|
<string id="39221">plex.tv wechsel OK</string>
|
||||||
|
|
||||||
<!-- Plex Artwork.py -->
|
<!-- Plex Artwork.py -->
|
||||||
<string id="39250">Alle Plex Bilder in Kodi zwischenzuspeichern kann sehr lange dauern. Möchten Sie wirklich fortfahren?</string>
|
<string id="39250">Alle Plex Bilder in Kodi zwischenzuspeichern kann sehr lange dauern. Möchten Sie wirklich fortfahren?</string>
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,18 +2,26 @@
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
import socket
|
import socket
|
||||||
|
import Queue
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
|
|
||||||
import utils
|
import utils
|
||||||
from plexbmchelper import listener, plexgdm, subscribers, functions, \
|
from plexbmchelper import listener, plexgdm, subscribers, functions, \
|
||||||
httppersist, settings
|
httppersist, settings
|
||||||
|
from PlexFunctions import ParseContainerKey, GetPlayQueue, \
|
||||||
|
ConvertPlexToKodiTime
|
||||||
|
import playlist
|
||||||
|
import player
|
||||||
|
|
||||||
|
|
||||||
@utils.logging
|
@utils.logging
|
||||||
@utils.ThreadMethodsAdditionalSuspend('emby_serverStatus')
|
@utils.ThreadMethodsAdditionalSuspend('plex_serverStatus')
|
||||||
@utils.ThreadMethods
|
@utils.ThreadMethods
|
||||||
class PlexCompanion(threading.Thread):
|
class PlexCompanion(threading.Thread):
|
||||||
|
"""
|
||||||
|
Initialize with a Queue for callbacks
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.logMsg("----===## Starting PlexCompanion ##===----", 1)
|
self.logMsg("----===## Starting PlexCompanion ##===----", 1)
|
||||||
self.settings = settings.getSettings()
|
self.settings = settings.getSettings()
|
||||||
|
@ -22,55 +30,150 @@ class PlexCompanion(threading.Thread):
|
||||||
self.client = plexgdm.plexgdm()
|
self.client = plexgdm.plexgdm()
|
||||||
self.client.clientDetails(self.settings)
|
self.client.clientDetails(self.settings)
|
||||||
self.logMsg("Registration string is: %s "
|
self.logMsg("Registration string is: %s "
|
||||||
% self.client.getClientDetails(), 1)
|
% self.client.getClientDetails(), 2)
|
||||||
|
|
||||||
|
# Initialize playlist/queue stuff
|
||||||
|
self.playlist = playlist.Playlist('video')
|
||||||
|
|
||||||
|
# kodi player instance
|
||||||
|
self.player = player.Player()
|
||||||
|
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
def _getStartItem(self, string):
|
||||||
|
"""
|
||||||
|
Grabs the Plex id from e.g. '/library/metadata/12987'
|
||||||
|
|
||||||
|
and returns the tuple (typus, id) where typus is either 'queueId' or
|
||||||
|
'plexId' and id is the corresponding id as a string
|
||||||
|
"""
|
||||||
|
typus = 'plexId'
|
||||||
|
if string.startswith('/library/metadata'):
|
||||||
|
try:
|
||||||
|
string = string.split('/')[3]
|
||||||
|
except IndexError:
|
||||||
|
string = ''
|
||||||
|
else:
|
||||||
|
self.logMsg('Unknown string! %s' % string, -1)
|
||||||
|
return typus, string
|
||||||
|
|
||||||
|
def processTasks(self, task):
|
||||||
|
"""
|
||||||
|
Processes tasks picked up e.g. by Companion listener
|
||||||
|
|
||||||
|
task = {
|
||||||
|
'action': 'playlist'
|
||||||
|
'data': as received from Plex companion
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
self.logMsg('Processing: %s' % task, 2)
|
||||||
|
data = task['data']
|
||||||
|
|
||||||
|
if task['action'] == 'playlist':
|
||||||
|
try:
|
||||||
|
_, queueId, query = ParseContainerKey(data['containerKey'])
|
||||||
|
except Exception as e:
|
||||||
|
self.logMsg('Exception while processing: %s' % e, -1)
|
||||||
|
import traceback
|
||||||
|
self.logMsg("Traceback:\n%s" % traceback.format_exc(), -1)
|
||||||
|
return
|
||||||
|
if self.playlist is not None:
|
||||||
|
if self.playlist.Typus() != data.get('type'):
|
||||||
|
self.logMsg('Switching to Kodi playlist of type %s'
|
||||||
|
% data.get('type'), 1)
|
||||||
|
self.playlist = None
|
||||||
|
if self.playlist is None:
|
||||||
|
if data.get('type') == 'music':
|
||||||
|
self.playlist = playlist.Playlist('music')
|
||||||
|
elif data.get('type') == 'video':
|
||||||
|
self.playlist = playlist.Playlist('video')
|
||||||
|
else:
|
||||||
|
self.playlist = playlist.Playlist()
|
||||||
|
if self.playlist is None:
|
||||||
|
self.logMsg('Could not initialize playlist', -1)
|
||||||
|
return
|
||||||
|
if queueId != self.playlist.QueueId():
|
||||||
|
self.logMsg('New playlist received, updating!', 1)
|
||||||
|
xml = GetPlayQueue(queueId)
|
||||||
|
if xml in (None, 401):
|
||||||
|
self.logMsg('Could not download Plex playlist.', -1)
|
||||||
|
return
|
||||||
|
# Clear existing playlist on the Kodi side
|
||||||
|
self.playlist.clear()
|
||||||
|
# Set new values
|
||||||
|
self.playlist.QueueId(queueId)
|
||||||
|
self.playlist.PlayQueueVersion(int(
|
||||||
|
xml.attrib.get('playQueueVersion')))
|
||||||
|
self.playlist.Guid(xml.attrib.get('guid'))
|
||||||
|
items = []
|
||||||
|
for item in xml:
|
||||||
|
items.append({
|
||||||
|
'playQueueItemID': item.get('playQueueItemID'),
|
||||||
|
'plexId': item.get('ratingKey'),
|
||||||
|
'kodiId': None})
|
||||||
|
self.playlist.playAll(
|
||||||
|
items,
|
||||||
|
startitem=self._getStartItem(data.get('key', '')),
|
||||||
|
offset=ConvertPlexToKodiTime(data.get('offset', 0)))
|
||||||
|
self.logMsg('Initiated playlist no %s with version %s'
|
||||||
|
% (self.playlist.QueueId(),
|
||||||
|
self.playlist.PlayQueueVersion()))
|
||||||
|
else:
|
||||||
|
self.logMsg('This has never happened before!', -1)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
httpd = False
|
||||||
# Cache for quicker while loops
|
# Cache for quicker while loops
|
||||||
log = self.logMsg
|
log = self.logMsg
|
||||||
client = self.client
|
client = self.client
|
||||||
threadStopped = self.threadStopped
|
threadStopped = self.threadStopped
|
||||||
threadSuspended = self.threadSuspended
|
threadSuspended = self.threadSuspended
|
||||||
start_count = 0
|
|
||||||
|
|
||||||
# Start up instances
|
# Start up instances
|
||||||
requestMgr = httppersist.RequestMgr()
|
requestMgr = httppersist.RequestMgr()
|
||||||
jsonClass = functions.jsonClass(requestMgr, self.settings)
|
jsonClass = functions.jsonClass(requestMgr, self.settings)
|
||||||
subscriptionManager = subscribers.SubscriptionManager(
|
subscriptionManager = subscribers.SubscriptionManager(
|
||||||
jsonClass, requestMgr)
|
jsonClass, requestMgr, self.player, self.playlist)
|
||||||
|
|
||||||
# Start up httpd
|
queue = Queue.Queue(maxsize=100)
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
httpd = listener.ThreadedHTTPServer(
|
|
||||||
client,
|
|
||||||
subscriptionManager,
|
|
||||||
jsonClass,
|
|
||||||
self.settings,
|
|
||||||
('', self.settings['myport']),
|
|
||||||
listener.MyHandler)
|
|
||||||
httpd.timeout = 0.95
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
log("Unable to start PlexCompanion. Traceback:", -1)
|
|
||||||
log(traceback.print_exc(), -1)
|
|
||||||
|
|
||||||
xbmc.sleep(3000)
|
if utils.settings('plexCompanion') == 'true':
|
||||||
|
self.logMsg('User activated Plex Companion', 0)
|
||||||
|
# Start up httpd
|
||||||
|
start_count = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
httpd = listener.ThreadedHTTPServer(
|
||||||
|
client,
|
||||||
|
subscriptionManager,
|
||||||
|
jsonClass,
|
||||||
|
self.settings,
|
||||||
|
queue,
|
||||||
|
('', self.settings['myport']),
|
||||||
|
listener.MyHandler)
|
||||||
|
httpd.timeout = 0.95
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
log("Unable to start PlexCompanion. Traceback:", -1)
|
||||||
|
log(traceback.print_exc(), -1)
|
||||||
|
|
||||||
if start_count == 3:
|
xbmc.sleep(3000)
|
||||||
log("Error: Unable to start web helper.", -1)
|
|
||||||
httpd = False
|
|
||||||
break
|
|
||||||
|
|
||||||
start_count += 1
|
if start_count == 3:
|
||||||
|
log("Error: Unable to start web helper.", -1)
|
||||||
|
httpd = False
|
||||||
|
break
|
||||||
|
|
||||||
if not httpd:
|
start_count += 1
|
||||||
return
|
else:
|
||||||
|
self.logMsg('User deactivated Plex Companion', 0)
|
||||||
|
|
||||||
client.start_all()
|
client.start_all()
|
||||||
|
|
||||||
message_count = 0
|
message_count = 0
|
||||||
|
if httpd:
|
||||||
|
t = threading.Thread(target=httpd.handle_request)
|
||||||
|
|
||||||
while not threadStopped():
|
while not threadStopped():
|
||||||
# If we are not authorized, sleep
|
# If we are not authorized, sleep
|
||||||
# Otherwise, we trigger a download which leads to a
|
# Otherwise, we trigger a download which leads to a
|
||||||
|
@ -80,34 +183,50 @@ class PlexCompanion(threading.Thread):
|
||||||
break
|
break
|
||||||
xbmc.sleep(1000)
|
xbmc.sleep(1000)
|
||||||
try:
|
try:
|
||||||
|
|
||||||
httpd.handle_request()
|
|
||||||
message_count += 1
|
message_count += 1
|
||||||
|
if httpd:
|
||||||
|
if not t.isAlive():
|
||||||
|
# Use threads cause the method will stall
|
||||||
|
t = threading.Thread(target=httpd.handle_request)
|
||||||
|
t.start()
|
||||||
|
|
||||||
if message_count > 100:
|
if message_count == 3000:
|
||||||
if client.check_client_registration():
|
message_count = 0
|
||||||
log("Client is still registered", 1)
|
if client.check_client_registration():
|
||||||
else:
|
log("Client is still registered", 1)
|
||||||
log("Client is no longer registered", 1)
|
else:
|
||||||
log("Plex Companion still running on port %s"
|
log("Client is no longer registered", 1)
|
||||||
% self.settings['myport'], 1)
|
log("Plex Companion still running on port %s"
|
||||||
message_count = 0
|
% self.settings['myport'], 1)
|
||||||
|
|
||||||
# Get and set servers
|
# Get and set servers
|
||||||
subscriptionManager.serverlist = client.getServerList()
|
if message_count % 30 == 0:
|
||||||
|
subscriptionManager.serverlist = client.getServerList()
|
||||||
subscriptionManager.notify()
|
subscriptionManager.notify()
|
||||||
xbmc.sleep(50)
|
if not httpd:
|
||||||
|
message_count = 0
|
||||||
except:
|
except:
|
||||||
log("Error in loop, continuing anyway", 0)
|
log("Error in loop, continuing anyway. Traceback:", 1)
|
||||||
log(traceback.format_exc(), 1)
|
log(traceback.format_exc(), 1)
|
||||||
xbmc.sleep(50)
|
# See if there's anything we need to process
|
||||||
|
try:
|
||||||
|
task = queue.get(block=False)
|
||||||
|
except Queue.Empty:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Got instructions, process them
|
||||||
|
self.processTasks(task)
|
||||||
|
queue.task_done()
|
||||||
|
# Don't sleep
|
||||||
|
continue
|
||||||
|
xbmc.sleep(20)
|
||||||
|
|
||||||
client.stop_all()
|
client.stop_all()
|
||||||
try:
|
if httpd:
|
||||||
httpd.socket.shutdown(socket.SHUT_RDWR)
|
try:
|
||||||
except:
|
httpd.socket.shutdown(socket.SHUT_RDWR)
|
||||||
pass
|
except:
|
||||||
finally:
|
pass
|
||||||
httpd.socket.close()
|
finally:
|
||||||
|
httpd.socket.close()
|
||||||
log("----===## Plex Companion stopped ##===----", 0)
|
log("----===## Plex Companion stopped ##===----", 0)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
from urlparse import urlparse, parse_qs
|
from urlparse import urlparse, parse_qsl
|
||||||
import re
|
import re
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
|
@ -86,13 +86,13 @@ def GetPlexKeyNumber(plexKey):
|
||||||
def ParseContainerKey(containerKey):
|
def ParseContainerKey(containerKey):
|
||||||
"""
|
"""
|
||||||
Parses e.g. /playQueues/3045?own=1&repeat=0&window=200 to:
|
Parses e.g. /playQueues/3045?own=1&repeat=0&window=200 to:
|
||||||
'playQueues', '3045', {'window': ['200'], 'own': ['1'], 'repeat': ['0']}
|
'playQueues', '3045', {'window': '200', 'own': '1', 'repeat': '0'}
|
||||||
|
|
||||||
Output hence: library, key, query (query as a special dict)
|
Output hence: library, key, query (str, str, dict)
|
||||||
"""
|
"""
|
||||||
result = urlparse(containerKey)
|
result = urlparse(containerKey)
|
||||||
library, key = GetPlexKeyNumber(result.path)
|
library, key = GetPlexKeyNumber(result.path)
|
||||||
query = parse_qs(result.query)
|
query = dict(parse_qsl(result.query))
|
||||||
return library, key, query
|
return library, key, query
|
||||||
|
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ def SelectStreams(url, args):
|
||||||
chosen.
|
chosen.
|
||||||
"""
|
"""
|
||||||
downloadutils.DownloadUtils().downloadUrl(
|
downloadutils.DownloadUtils().downloadUrl(
|
||||||
url + '?' + urlencode(args), type='PUT')
|
url + '?' + urlencode(args), action_type='PUT')
|
||||||
|
|
||||||
|
|
||||||
def GetPlayQueue(playQueueID):
|
def GetPlayQueue(playQueueID):
|
||||||
|
@ -165,7 +165,7 @@ def GetPlexMetadata(key):
|
||||||
Can be called with either Plex key '/library/metadata/xxxx'metadata
|
Can be called with either Plex key '/library/metadata/xxxx'metadata
|
||||||
OR with the digits 'xxxx' only.
|
OR with the digits 'xxxx' only.
|
||||||
|
|
||||||
Returns None if something went wrong
|
Returns None or 401 if something went wrong
|
||||||
"""
|
"""
|
||||||
key = str(key)
|
key = str(key)
|
||||||
if '/library/metadata/' in key:
|
if '/library/metadata/' in key:
|
||||||
|
@ -173,14 +173,15 @@ def GetPlexMetadata(key):
|
||||||
else:
|
else:
|
||||||
url = "{server}/library/metadata/" + key
|
url = "{server}/library/metadata/" + key
|
||||||
arguments = {
|
arguments = {
|
||||||
'checkFiles': 1, # No idea
|
'checkFiles': 0,
|
||||||
'includeExtras': 1, # Trailers and Extras => Extras
|
'includeExtras': 1, # Trailers and Extras => Extras
|
||||||
# 'includeRelated': 1, # Similar movies => Video -> Related
|
'includeReviews': 1,
|
||||||
# 'includeRelatedCount': 5,
|
'includeRelated': 0, # Similar movies => Video -> Related
|
||||||
|
# 'includeRelatedCount': 0,
|
||||||
# 'includeOnDeck': 1,
|
# 'includeOnDeck': 1,
|
||||||
'includeChapters': 1,
|
# 'includeChapters': 1,
|
||||||
'includePopularLeaves': 1,
|
# 'includePopularLeaves': 1,
|
||||||
'includeConcerts': 1
|
# 'includeConcerts': 1
|
||||||
}
|
}
|
||||||
url = url + '?' + urlencode(arguments)
|
url = url + '?' + urlencode(arguments)
|
||||||
xml = downloadutils.DownloadUtils().downloadUrl(url)
|
xml = downloadutils.DownloadUtils().downloadUrl(url)
|
||||||
|
@ -388,7 +389,7 @@ def GetPlexPlaylist(itemid, librarySectionUUID, mediatype='movie'):
|
||||||
'repeat': '0'
|
'repeat': '0'
|
||||||
}
|
}
|
||||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||||
url + '?' + urlencode(args), type="POST")
|
url + '?' + urlencode(args), action_type="POST")
|
||||||
try:
|
try:
|
||||||
xml[0].tag
|
xml[0].tag
|
||||||
except (IndexError, TypeError, AttributeError):
|
except (IndexError, TypeError, AttributeError):
|
||||||
|
@ -424,14 +425,14 @@ def PMSHttpsEnabled(url):
|
||||||
verifySSL=False)
|
verifySSL=False)
|
||||||
try:
|
try:
|
||||||
res.attrib
|
res.attrib
|
||||||
except:
|
except AttributeError:
|
||||||
# Might have SSL deactivated. Try with http
|
# Might have SSL deactivated. Try with http
|
||||||
res = doUtils('http://%s/identity' % url,
|
res = doUtils('http://%s/identity' % url,
|
||||||
authenticate=False,
|
authenticate=False,
|
||||||
verifySSL=False)
|
verifySSL=False)
|
||||||
try:
|
try:
|
||||||
res.attrib
|
res.attrib
|
||||||
except:
|
except AttributeError:
|
||||||
logMsg(title, "Could not contact PMS %s" % url, -1)
|
logMsg(title, "Could not contact PMS %s" % url, -1)
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
|
@ -448,16 +449,17 @@ def GetMachineIdentifier(url):
|
||||||
|
|
||||||
Returns None if something went wrong
|
Returns None if something went wrong
|
||||||
"""
|
"""
|
||||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
xml = downloadutils.DownloadUtils().downloadUrl('%s/identity' % url,
|
||||||
url + '/identity', type="GET")
|
authenticate=False,
|
||||||
|
verifySSL=False,
|
||||||
|
timeout=4)
|
||||||
try:
|
try:
|
||||||
xml.attrib
|
machineIdentifier = xml.attrib['machineIdentifier']
|
||||||
except:
|
except (AttributeError, KeyError):
|
||||||
logMsg(title, 'Could not get the PMS machineIdentifier for %s'
|
logMsg(title, 'Could not get the PMS machineIdentifier for %s'
|
||||||
% url, -1)
|
% url, -1)
|
||||||
return None
|
return None
|
||||||
machineIdentifier = xml.attrib.get('machineIdentifier')
|
logMsg(title, 'Found machineIdentifier %s for the PMS %s'
|
||||||
logMsg(title, 'Found machineIdentifier %s for %s'
|
|
||||||
% (machineIdentifier, url), 1)
|
% (machineIdentifier, url), 1)
|
||||||
return machineIdentifier
|
return machineIdentifier
|
||||||
|
|
||||||
|
@ -480,7 +482,6 @@ def GetPMSStatus(token):
|
||||||
answer = {}
|
answer = {}
|
||||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||||
'{server}/status/sessions',
|
'{server}/status/sessions',
|
||||||
type="GET",
|
|
||||||
headerOptions={'X-Plex-Token': token})
|
headerOptions={'X-Plex-Token': token})
|
||||||
try:
|
try:
|
||||||
xml.attrib
|
xml.attrib
|
||||||
|
@ -519,5 +520,5 @@ def scrobble(ratingKey, state):
|
||||||
url = "{server}/:/unscrobble?" + urlencode(args)
|
url = "{server}/:/unscrobble?" + urlencode(args)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
downloadutils.DownloadUtils().downloadUrl(url, type="GET")
|
downloadutils.DownloadUtils().downloadUrl(url)
|
||||||
logMsg(title, "Toggled watched state for Plex item %s" % ratingKey, 1)
|
logMsg(title, "Toggled watched state for Plex item %s" % ratingKey, 1)
|
||||||
|
|
|
@ -28,7 +28,7 @@ class Artwork():
|
||||||
xbmc_port = None
|
xbmc_port = None
|
||||||
xbmc_username = None
|
xbmc_username = None
|
||||||
xbmc_password = None
|
xbmc_password = None
|
||||||
|
|
||||||
imageCacheThreads = []
|
imageCacheThreads = []
|
||||||
imageCacheLimitThreads = 0
|
imageCacheLimitThreads = 0
|
||||||
|
|
||||||
|
@ -36,9 +36,9 @@ class Artwork():
|
||||||
|
|
||||||
self.enableTextureCache = utils.settings('enableTextureCache') == "true"
|
self.enableTextureCache = utils.settings('enableTextureCache') == "true"
|
||||||
self.imageCacheLimitThreads = int(utils.settings("imageCacheLimit"))
|
self.imageCacheLimitThreads = int(utils.settings("imageCacheLimit"))
|
||||||
self.imageCacheLimitThreads = int(self.imageCacheLimitThreads * 5);
|
self.imageCacheLimitThreads = int(self.imageCacheLimitThreads * 5)
|
||||||
utils.logMsg("Using Image Cache Thread Count: " + str(self.imageCacheLimitThreads), 1)
|
utils.logMsg("Using Image Cache Thread Count: " + str(self.imageCacheLimitThreads), 1)
|
||||||
|
|
||||||
if not self.xbmc_port and self.enableTextureCache:
|
if not self.xbmc_port and self.enableTextureCache:
|
||||||
self.setKodiWebServerDetails()
|
self.setKodiWebServerDetails()
|
||||||
|
|
||||||
|
@ -48,15 +48,15 @@ class Artwork():
|
||||||
def double_urlencode(self, text):
|
def double_urlencode(self, text):
|
||||||
text = self.single_urlencode(text)
|
text = self.single_urlencode(text)
|
||||||
text = self.single_urlencode(text)
|
text = self.single_urlencode(text)
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def single_urlencode(self, text):
|
def single_urlencode(self, text):
|
||||||
|
|
||||||
text = urllib.urlencode({'blahblahblah':text.encode("utf-8")}) #urlencode needs a utf- string
|
text = urllib.urlencode({'blahblahblah': utils.tryEncode(text)}) #urlencode needs a utf- string
|
||||||
text = text[13:]
|
text = text[13:]
|
||||||
|
|
||||||
return text.decode("utf-8") #return the result again as unicode
|
return utils.tryDecode(text) #return the result again as unicode
|
||||||
|
|
||||||
def setKodiWebServerDetails(self):
|
def setKodiWebServerDetails(self):
|
||||||
# Get the Kodi webserver details - used to set the texture cache
|
# Get the Kodi webserver details - used to set the texture cache
|
||||||
|
@ -74,9 +74,9 @@ class Artwork():
|
||||||
result = json.loads(result)
|
result = json.loads(result)
|
||||||
try:
|
try:
|
||||||
xbmc_webserver_enabled = result['result']['value']
|
xbmc_webserver_enabled = result['result']['value']
|
||||||
except KeyError, TypeError:
|
except (KeyError, TypeError):
|
||||||
xbmc_webserver_enabled = False
|
xbmc_webserver_enabled = False
|
||||||
|
|
||||||
if not xbmc_webserver_enabled:
|
if not xbmc_webserver_enabled:
|
||||||
# Enable the webserver, it is disabled
|
# Enable the webserver, it is disabled
|
||||||
web_port = {
|
web_port = {
|
||||||
|
@ -159,7 +159,7 @@ class Artwork():
|
||||||
self.xbmc_password = result['result']['value']
|
self.xbmc_password = result['result']['value']
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def FullTextureCacheSync(self):
|
def FullTextureCacheSync(self):
|
||||||
# This method will sync all Kodi artwork to textures13.db
|
# This method will sync all Kodi artwork to textures13.db
|
||||||
# and cache them locally. This takes diskspace!
|
# and cache them locally. This takes diskspace!
|
||||||
|
@ -167,30 +167,34 @@ class Artwork():
|
||||||
string = xbmcaddon.Addon().getLocalizedString
|
string = xbmcaddon.Addon().getLocalizedString
|
||||||
|
|
||||||
if not xbmcgui.Dialog().yesno(
|
if not xbmcgui.Dialog().yesno(
|
||||||
"Image Texture Cache", string(39250).encode('utf-8')):
|
"Image Texture Cache", string(39250)):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.logMsg("Doing Image Cache Sync", 1)
|
self.logMsg("Doing Image Cache Sync", 1)
|
||||||
|
|
||||||
dialog = xbmcgui.DialogProgress()
|
dialog = xbmcgui.DialogProgress()
|
||||||
dialog.create("Emby for Kodi", "Image Cache Sync")
|
dialog.create("PlexKodiConnect", "Image Cache Sync")
|
||||||
|
|
||||||
# ask to rest all existing or not
|
# ask to rest all existing or not
|
||||||
if xbmcgui.Dialog().yesno(
|
if xbmcgui.Dialog().yesno(
|
||||||
"Image Texture Cache", string(39251).encode('utf-8'), ""):
|
"Image Texture Cache", string(39251), ""):
|
||||||
self.logMsg("Resetting all cache data first", 1)
|
self.logMsg("Resetting all cache data first", 1)
|
||||||
# Remove all existing textures first
|
# Remove all existing textures first
|
||||||
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
|
path = utils.tryDecode(xbmc.translatePath("special://thumbnails/"))
|
||||||
if utils.IfExists(path):
|
if utils.IfExists(path):
|
||||||
allDirs, allFiles = xbmcvfs.listdir(path)
|
allDirs, allFiles = xbmcvfs.listdir(path)
|
||||||
for dir in allDirs:
|
for dir in allDirs:
|
||||||
allDirs, allFiles = xbmcvfs.listdir(path+dir)
|
allDirs, allFiles = xbmcvfs.listdir(path+dir)
|
||||||
for file in allFiles:
|
for file in allFiles:
|
||||||
if os.path.supports_unicode_filenames:
|
if os.path.supports_unicode_filenames:
|
||||||
xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8')))
|
xbmcvfs.delete(os.path.join(
|
||||||
|
path + utils.tryDecode(dir),
|
||||||
|
utils.tryDecode(file)))
|
||||||
else:
|
else:
|
||||||
xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file))
|
xbmcvfs.delete(os.path.join(
|
||||||
|
utils.tryEncode(path) + dir,
|
||||||
|
file))
|
||||||
|
|
||||||
# remove all existing data from texture DB
|
# remove all existing data from texture DB
|
||||||
textureconnection = utils.kodiSQL('texture')
|
textureconnection = utils.kodiSQL('texture')
|
||||||
texturecursor = textureconnection.cursor()
|
texturecursor = textureconnection.cursor()
|
||||||
|
@ -209,8 +213,8 @@ class Artwork():
|
||||||
cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors
|
cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors
|
||||||
result = cursor.fetchall()
|
result = cursor.fetchall()
|
||||||
total = len(result)
|
total = len(result)
|
||||||
count = 1
|
count = 1
|
||||||
percentage = 0
|
percentage = 0
|
||||||
self.logMsg("Image cache sync about to process " + str(total) + " images", 1)
|
self.logMsg("Image cache sync about to process " + str(total) + " images", 1)
|
||||||
for url in result:
|
for url in result:
|
||||||
if dialog.iscanceled():
|
if dialog.iscanceled():
|
||||||
|
@ -221,26 +225,26 @@ class Artwork():
|
||||||
self.CacheTexture(url[0])
|
self.CacheTexture(url[0])
|
||||||
count += 1
|
count += 1
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
# Cache all entries in music DB
|
# Cache all entries in music DB
|
||||||
connection = utils.kodiSQL('music')
|
connection = utils.kodiSQL('music')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute("SELECT url FROM art")
|
cursor.execute("SELECT url FROM art")
|
||||||
result = cursor.fetchall()
|
result = cursor.fetchall()
|
||||||
total = len(result)
|
total = len(result)
|
||||||
count = 1
|
count = 1
|
||||||
percentage = 0
|
percentage = 0
|
||||||
self.logMsg("Image cache sync about to process " + str(total) + " images", 1)
|
self.logMsg("Image cache sync about to process " + str(total) + " images", 1)
|
||||||
for url in result:
|
for url in result:
|
||||||
if dialog.iscanceled():
|
if dialog.iscanceled():
|
||||||
break
|
break
|
||||||
percentage = int((float(count) / float(total))*100)
|
percentage = int((float(count) / float(total))*100)
|
||||||
textMessage = str(count) + " of " + str(total)
|
textMessage = str(count) + " of " + str(total)
|
||||||
dialog.update(percentage, "Updating Image Cache: " + textMessage)
|
dialog.update(percentage, "Updating Image Cache: " + textMessage)
|
||||||
self.CacheTexture(url[0])
|
self.CacheTexture(url[0])
|
||||||
count += 1
|
count += 1
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads)))
|
dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads)))
|
||||||
self.logMsg("Waiting for all threads to exit", 1)
|
self.logMsg("Waiting for all threads to exit", 1)
|
||||||
while len(self.imageCacheThreads) > 0:
|
while len(self.imageCacheThreads) > 0:
|
||||||
|
@ -250,16 +254,16 @@ class Artwork():
|
||||||
dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads)))
|
dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads)))
|
||||||
self.logMsg("Waiting for all threads to exit: " + str(len(self.imageCacheThreads)), 1)
|
self.logMsg("Waiting for all threads to exit: " + str(len(self.imageCacheThreads)), 1)
|
||||||
xbmc.sleep(500)
|
xbmc.sleep(500)
|
||||||
|
|
||||||
dialog.close()
|
dialog.close()
|
||||||
|
|
||||||
def addWorkerImageCacheThread(self, urlToAdd):
|
def addWorkerImageCacheThread(self, urlToAdd):
|
||||||
|
|
||||||
while(True):
|
while(True):
|
||||||
# removed finished
|
# removed finished
|
||||||
for thread in self.imageCacheThreads:
|
for thread in self.imageCacheThreads:
|
||||||
if thread.isFinished:
|
if thread.isFinished:
|
||||||
self.imageCacheThreads.remove(thread)
|
self.imageCacheThreads.remove(thread)
|
||||||
|
|
||||||
# add a new thread or wait and retry if we hit our limit
|
# add a new thread or wait and retry if we hit our limit
|
||||||
if(len(self.imageCacheThreads) < self.imageCacheLimitThreads):
|
if(len(self.imageCacheThreads) < self.imageCacheLimitThreads):
|
||||||
|
@ -273,14 +277,14 @@ class Artwork():
|
||||||
else:
|
else:
|
||||||
self.logMsg("Waiting for empty queue spot: " + str(len(self.imageCacheThreads)), 2)
|
self.logMsg("Waiting for empty queue spot: " + str(len(self.imageCacheThreads)), 2)
|
||||||
xbmc.sleep(50)
|
xbmc.sleep(50)
|
||||||
|
|
||||||
|
|
||||||
def CacheTexture(self, url):
|
def CacheTexture(self, url):
|
||||||
# Cache a single image url to the texture cache
|
# Cache a single image url to the texture cache
|
||||||
if url and self.enableTextureCache:
|
if url and self.enableTextureCache:
|
||||||
if(self.imageCacheLimitThreads == 0 or self.imageCacheLimitThreads == None):
|
if(self.imageCacheLimitThreads == 0 or self.imageCacheLimitThreads == None):
|
||||||
#Add image to texture cache by simply calling it at the http endpoint
|
#Add image to texture cache by simply calling it at the http endpoint
|
||||||
|
|
||||||
url = self.double_urlencode(url)
|
url = self.double_urlencode(url)
|
||||||
try: # Extreme short timeouts so we will have a exception.
|
try: # Extreme short timeouts so we will have a exception.
|
||||||
response = requests.head(
|
response = requests.head(
|
||||||
|
@ -291,7 +295,7 @@ class Artwork():
|
||||||
timeout=(0.01, 0.01))
|
timeout=(0.01, 0.01))
|
||||||
# We don't need the result
|
# We don't need the result
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.addWorkerImageCacheThread(url)
|
self.addWorkerImageCacheThread(url)
|
||||||
|
|
||||||
|
@ -349,13 +353,13 @@ class Artwork():
|
||||||
mediaType=mediaType,
|
mediaType=mediaType,
|
||||||
imageType="%s%s" % ("fanart", index),
|
imageType="%s%s" % ("fanart", index),
|
||||||
cursor=cursor)
|
cursor=cursor)
|
||||||
|
|
||||||
if backdropsNumber > 1:
|
if backdropsNumber > 1:
|
||||||
try: # Will only fail on the first try, str to int.
|
try: # Will only fail on the first try, str to int.
|
||||||
index += 1
|
index += 1
|
||||||
except TypeError:
|
except TypeError:
|
||||||
index = 1
|
index = 1
|
||||||
|
|
||||||
elif art == "Primary":
|
elif art == "Primary":
|
||||||
# Primary art is processed as thumb and poster for Kodi.
|
# Primary art is processed as thumb and poster for Kodi.
|
||||||
for artType in kodiart[art]:
|
for artType in kodiart[art]:
|
||||||
|
@ -365,7 +369,7 @@ class Artwork():
|
||||||
mediaType=mediaType,
|
mediaType=mediaType,
|
||||||
imageType=artType,
|
imageType=artType,
|
||||||
cursor=cursor)
|
cursor=cursor)
|
||||||
|
|
||||||
elif kodiart.get(art):
|
elif kodiart.get(art):
|
||||||
# Process the rest artwork type that Kodi can use
|
# Process the rest artwork type that Kodi can use
|
||||||
self.addOrUpdateArt(
|
self.addOrUpdateArt(
|
||||||
|
@ -391,9 +395,11 @@ class Artwork():
|
||||||
cursor.execute(query, (kodiId, mediaType, imageType,))
|
cursor.execute(query, (kodiId, mediaType, imageType,))
|
||||||
try: # Update the artwork
|
try: # Update the artwork
|
||||||
url = cursor.fetchone()[0]
|
url = cursor.fetchone()[0]
|
||||||
|
|
||||||
except TypeError: # Add the artwork
|
except TypeError: # Add the artwork
|
||||||
cacheimage = True
|
cacheimage = True
|
||||||
|
self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2)
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
'''
|
'''
|
||||||
INSERT INTO art(media_id, media_type, type, url)
|
INSERT INTO art(media_id, media_type, type, url)
|
||||||
|
@ -402,17 +408,21 @@ class Artwork():
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
|
cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
|
||||||
|
|
||||||
else: # Only cache artwork if it changed
|
else: # Only cache artwork if it changed
|
||||||
if url != imageUrl:
|
if url != imageUrl:
|
||||||
cacheimage = True
|
cacheimage = True
|
||||||
|
|
||||||
# Only for the main backdrop, poster
|
# Only for the main backdrop, poster
|
||||||
if (utils.window('emby_initialScan') != "true" and
|
if (utils.window('plex_initialScan') != "true" and
|
||||||
imageType in ("fanart", "poster")):
|
imageType in ("fanart", "poster")):
|
||||||
# Delete current entry before updating with the new one
|
# Delete current entry before updating with the new one
|
||||||
self.deleteCachedArtwork(url)
|
self.deleteCachedArtwork(url)
|
||||||
|
|
||||||
|
self.logMsg(
|
||||||
|
"Updating Art url for %s kodiId: %s (%s) -> (%s)"
|
||||||
|
% (imageType, kodiId, url, imageUrl), 1)
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
"UPDATE art",
|
"UPDATE art",
|
||||||
|
@ -422,9 +432,9 @@ class Artwork():
|
||||||
"AND type = ?"
|
"AND type = ?"
|
||||||
))
|
))
|
||||||
cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
|
cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
|
||||||
|
|
||||||
# Cache fanart and poster in Kodi texture cache
|
# Cache fanart and poster in Kodi texture cache
|
||||||
if cacheimage and imageType in ("fanart", "poster", "thumb"):
|
if cacheimage:
|
||||||
self.CacheTexture(imageUrl)
|
self.CacheTexture(imageUrl)
|
||||||
|
|
||||||
def deleteArtwork(self, kodiid, mediatype, cursor):
|
def deleteArtwork(self, kodiid, mediatype, cursor):
|
||||||
|
@ -453,24 +463,25 @@ class Artwork():
|
||||||
try:
|
try:
|
||||||
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
|
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
|
||||||
cachedurl = cursor.fetchone()[0]
|
cachedurl = cursor.fetchone()[0]
|
||||||
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
self.logMsg("Could not find cached url.", 1)
|
self.logMsg("Could not find cached url.", 1)
|
||||||
|
|
||||||
except OperationalError:
|
except OperationalError:
|
||||||
self.logMsg("Database is locked. Skip deletion process.", 1)
|
self.logMsg("Database is locked. Skip deletion process.", 1)
|
||||||
|
|
||||||
else: # Delete thumbnail as well as the entry
|
else: # Delete thumbnail as well as the entry
|
||||||
thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl).decode('utf-8')
|
thumbnails = utils.tryDecode(
|
||||||
|
xbmc.translatePath("special://thumbnails/%s" % cachedurl))
|
||||||
self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1)
|
self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1)
|
||||||
xbmcvfs.delete(thumbnails)
|
xbmcvfs.delete(thumbnails)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
|
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
|
||||||
connection.commit()
|
connection.commit()
|
||||||
except OperationalError:
|
except OperationalError:
|
||||||
self.logMsg("Issue deleting url from cache. Skipping.", 2)
|
self.logMsg("Issue deleting url from cache. Skipping.", 2)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
|
@ -487,7 +498,7 @@ class Artwork():
|
||||||
"%s/emby/Items/%s/Images/Primary?"
|
"%s/emby/Items/%s/Images/Primary?"
|
||||||
"MaxWidth=400&MaxHeight=400&Index=0&Tag=%s"
|
"MaxWidth=400&MaxHeight=400&Index=0&Tag=%s"
|
||||||
% (self.server, personId, tag))
|
% (self.server, personId, tag))
|
||||||
|
|
||||||
person['imageurl'] = image
|
person['imageurl'] = image
|
||||||
|
|
||||||
return people
|
return people
|
||||||
|
@ -501,8 +512,6 @@ class Artwork():
|
||||||
|
|
||||||
def getAllArtwork(self, item, parentInfo=False):
|
def getAllArtwork(self, item, parentInfo=False):
|
||||||
|
|
||||||
server = self.server
|
|
||||||
|
|
||||||
itemid = item['Id']
|
itemid = item['Id']
|
||||||
artworks = item['ImageTags']
|
artworks = item['ImageTags']
|
||||||
backdrops = item.get('BackdropImageTags',[])
|
backdrops = item.get('BackdropImageTags',[])
|
||||||
|
@ -527,13 +536,13 @@ def getAllArtwork(self, item, parentInfo=False):
|
||||||
'Disc': "",
|
'Disc': "",
|
||||||
'Backdrop': []
|
'Backdrop': []
|
||||||
}
|
}
|
||||||
|
|
||||||
# Process backdrops
|
# Process backdrops
|
||||||
for index, tag in enumerate(backdrops):
|
for index, tag in enumerate(backdrops):
|
||||||
artwork = (
|
artwork = (
|
||||||
"%s/emby/Items/%s/Images/Backdrop/%s?"
|
"%s/emby/Items/%s/Images/Backdrop/%s?"
|
||||||
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
||||||
% (server, itemid, index, maxWidth, maxHeight, tag, customquery))
|
% (self.server, itemid, index, maxWidth, maxHeight, tag, customquery))
|
||||||
allartworks['Backdrop'].append(artwork)
|
allartworks['Backdrop'].append(artwork)
|
||||||
|
|
||||||
# Process the rest of the artwork
|
# Process the rest of the artwork
|
||||||
|
@ -544,15 +553,15 @@ def getAllArtwork(self, item, parentInfo=False):
|
||||||
artwork = (
|
artwork = (
|
||||||
"%s/emby/Items/%s/Images/%s/0?"
|
"%s/emby/Items/%s/Images/%s/0?"
|
||||||
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
||||||
% (server, itemid, art, maxWidth, maxHeight, tag, customquery))
|
% (self.server, itemid, art, maxWidth, maxHeight, tag, customquery))
|
||||||
allartworks[art] = artwork
|
allartworks[art] = artwork
|
||||||
|
|
||||||
# Process parent items if the main item is missing artwork
|
# Process parent items if the main item is missing artwork
|
||||||
if parentInfo:
|
if parentInfo:
|
||||||
|
|
||||||
# Process parent backdrops
|
# Process parent backdrops
|
||||||
if not allartworks['Backdrop']:
|
if not allartworks['Backdrop']:
|
||||||
|
|
||||||
parentId = item.get('ParentBackdropItemId')
|
parentId = item.get('ParentBackdropItemId')
|
||||||
if parentId:
|
if parentId:
|
||||||
# If there is a parentId, go through the parent backdrop list
|
# If there is a parentId, go through the parent backdrop list
|
||||||
|
@ -562,7 +571,7 @@ def getAllArtwork(self, item, parentInfo=False):
|
||||||
artwork = (
|
artwork = (
|
||||||
"%s/emby/Items/%s/Images/Backdrop/%s?"
|
"%s/emby/Items/%s/Images/Backdrop/%s?"
|
||||||
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
||||||
% (server, parentId, index, maxWidth, maxHeight, tag, customquery))
|
% (self.server, parentId, index, maxWidth, maxHeight, tag, customquery))
|
||||||
allartworks['Backdrop'].append(artwork)
|
allartworks['Backdrop'].append(artwork)
|
||||||
|
|
||||||
# Process the rest of the artwork
|
# Process the rest of the artwork
|
||||||
|
@ -570,15 +579,15 @@ def getAllArtwork(self, item, parentInfo=False):
|
||||||
for parentart in parentartwork:
|
for parentart in parentartwork:
|
||||||
|
|
||||||
if not allartworks[parentart]:
|
if not allartworks[parentart]:
|
||||||
|
|
||||||
parentId = item.get('Parent%sItemId' % parentart)
|
parentId = item.get('Parent%sItemId' % parentart)
|
||||||
if parentId:
|
if parentId:
|
||||||
|
|
||||||
parentTag = item['Parent%sImageTag' % parentart]
|
parentTag = item['Parent%sImageTag' % parentart]
|
||||||
artwork = (
|
artwork = (
|
||||||
"%s/emby/Items/%s/Images/%s/0?"
|
"%s/emby/Items/%s/Images/%s/0?"
|
||||||
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
||||||
% (server, parentId, parentart,
|
% (self.server, parentId, parentart,
|
||||||
maxWidth, maxHeight, parentTag, customquery))
|
maxWidth, maxHeight, parentTag, customquery))
|
||||||
allartworks[parentart] = artwork
|
allartworks[parentart] = artwork
|
||||||
|
|
||||||
|
@ -587,12 +596,12 @@ def getAllArtwork(self, item, parentInfo=False):
|
||||||
|
|
||||||
parentId = item.get('AlbumId')
|
parentId = item.get('AlbumId')
|
||||||
if parentId and item.get('AlbumPrimaryImageTag'):
|
if parentId and item.get('AlbumPrimaryImageTag'):
|
||||||
|
|
||||||
parentTag = item['AlbumPrimaryImageTag']
|
parentTag = item['AlbumPrimaryImageTag']
|
||||||
artwork = (
|
artwork = (
|
||||||
"%s/emby/Items/%s/Images/Primary/0?"
|
"%s/emby/Items/%s/Images/Primary/0?"
|
||||||
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
||||||
% (server, parentId, maxWidth, maxHeight, parentTag, customquery))
|
% (self.server, parentId, maxWidth, maxHeight, parentTag, customquery))
|
||||||
allartworks['Primary'] = artwork
|
allartworks['Primary'] = artwork
|
||||||
|
|
||||||
return allartworks
|
return allartworks
|
||||||
|
|
|
@ -7,7 +7,7 @@ from uuid import uuid4
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
|
|
||||||
from utils import logging, window, settings
|
from utils import logging, window, settings, tryDecode
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -67,8 +67,7 @@ class ClientInfo():
|
||||||
def getDeviceName(self):
|
def getDeviceName(self):
|
||||||
if settings('deviceNameOpt') == "false":
|
if settings('deviceNameOpt') == "false":
|
||||||
# Use Kodi's deviceName
|
# Use Kodi's deviceName
|
||||||
deviceName = xbmc.getInfoLabel(
|
deviceName = tryDecode(xbmc.getInfoLabel('System.FriendlyName'))
|
||||||
'System.FriendlyName').decode('utf-8')
|
|
||||||
else:
|
else:
|
||||||
deviceName = settings('deviceName')
|
deviceName = settings('deviceName')
|
||||||
deviceName = deviceName.replace("\"", "_")
|
deviceName = deviceName.replace("\"", "_")
|
||||||
|
|
|
@ -21,7 +21,7 @@ requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
|
||||||
|
|
||||||
|
|
||||||
class ConnectUtils():
|
class ConnectUtils():
|
||||||
|
|
||||||
# Borg - multiple instances, shared state
|
# Borg - multiple instances, shared state
|
||||||
_shared_state = {}
|
_shared_state = {}
|
||||||
clientInfo = clientinfo.ClientInfo()
|
clientInfo = clientinfo.ClientInfo()
|
||||||
|
@ -60,8 +60,6 @@ class ConnectUtils():
|
||||||
|
|
||||||
def startSession(self):
|
def startSession(self):
|
||||||
|
|
||||||
log = self.logMsg
|
|
||||||
|
|
||||||
self.deviceId = self.clientInfo.getDeviceId()
|
self.deviceId = self.clientInfo.getDeviceId()
|
||||||
|
|
||||||
# User is identified from this point
|
# User is identified from this point
|
||||||
|
@ -75,8 +73,8 @@ class ConnectUtils():
|
||||||
if self.sslclient is not None:
|
if self.sslclient is not None:
|
||||||
verify = self.sslclient
|
verify = self.sslclient
|
||||||
except:
|
except:
|
||||||
log("Could not load SSL settings.", 1)
|
self.logMsg("Could not load SSL settings.", 1)
|
||||||
|
|
||||||
# Start session
|
# Start session
|
||||||
self.c = requests.Session()
|
self.c = requests.Session()
|
||||||
self.c.headers = header
|
self.c.headers = header
|
||||||
|
@ -85,7 +83,7 @@ class ConnectUtils():
|
||||||
self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
|
self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
|
||||||
self.c.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
|
self.c.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
|
||||||
|
|
||||||
log("Requests session started on: %s" % self.server, 1)
|
self.logMsg("Requests session started on: %s" % self.server, 1)
|
||||||
|
|
||||||
def stopSession(self):
|
def stopSession(self):
|
||||||
try:
|
try:
|
||||||
|
@ -95,8 +93,7 @@ class ConnectUtils():
|
||||||
|
|
||||||
def getHeader(self, authenticate=True):
|
def getHeader(self, authenticate=True):
|
||||||
|
|
||||||
clientInfo = self.clientInfo
|
version = self.clientInfo.getVersion()
|
||||||
version = clientInfo.getVersion()
|
|
||||||
|
|
||||||
if not authenticate:
|
if not authenticate:
|
||||||
# If user is not authenticated
|
# If user is not authenticated
|
||||||
|
@ -105,9 +102,9 @@ class ConnectUtils():
|
||||||
'X-Application': "Kodi/%s" % version,
|
'X-Application': "Kodi/%s" % version,
|
||||||
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
'Accept': "application/json"
|
'Accept': "application/json"
|
||||||
}
|
}
|
||||||
self.logMsg("Header: %s" % header, 1)
|
self.logMsg("Header: %s" % header, 1)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
token = self.token
|
token = self.token
|
||||||
# Attached to the requests session
|
# Attached to the requests session
|
||||||
|
@ -117,18 +114,17 @@ class ConnectUtils():
|
||||||
'Accept': "application/json",
|
'Accept': "application/json",
|
||||||
'X-Application': "Kodi/%s" % version,
|
'X-Application': "Kodi/%s" % version,
|
||||||
'X-Connect-UserToken': token
|
'X-Connect-UserToken': token
|
||||||
}
|
}
|
||||||
self.logMsg("Header: %s" % header, 1)
|
self.logMsg("Header: %s" % header, 1)
|
||||||
|
|
||||||
return header
|
return header
|
||||||
|
|
||||||
def doUrl(self, url, data=None, postBody=None, rtype="GET",
|
def doUrl(self, url, data=None, postBody=None, rtype="GET",
|
||||||
parameters=None, authenticate=True, timeout=None):
|
parameters=None, authenticate=True, timeout=None):
|
||||||
|
|
||||||
log = self.logMsg
|
|
||||||
window = utils.window
|
window = utils.window
|
||||||
|
|
||||||
log("=== ENTER connectUrl ===", 2)
|
self.logMsg("=== ENTER connectUrl ===", 2)
|
||||||
default_link = ""
|
default_link = ""
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
timeout = self.timeout
|
timeout = self.timeout
|
||||||
|
@ -137,7 +133,7 @@ class ConnectUtils():
|
||||||
try:
|
try:
|
||||||
# If connect user is authenticated
|
# If connect user is authenticated
|
||||||
if authenticate:
|
if authenticate:
|
||||||
try:
|
try:
|
||||||
c = self.c
|
c = self.c
|
||||||
# Replace for the real values
|
# Replace for the real values
|
||||||
url = url.replace("{server}", self.server)
|
url = url.replace("{server}", self.server)
|
||||||
|
@ -167,7 +163,7 @@ class ConnectUtils():
|
||||||
verifyssl = self.sslclient
|
verifyssl = self.sslclient
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Prepare request
|
# Prepare request
|
||||||
if rtype == "GET":
|
if rtype == "GET":
|
||||||
r = requests.get(url,
|
r = requests.get(url,
|
||||||
|
@ -195,7 +191,7 @@ class ConnectUtils():
|
||||||
verifyssl = self.sslclient
|
verifyssl = self.sslclient
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Prepare request
|
# Prepare request
|
||||||
if rtype == "GET":
|
if rtype == "GET":
|
||||||
r = requests.get(url,
|
r = requests.get(url,
|
||||||
|
@ -213,28 +209,28 @@ class ConnectUtils():
|
||||||
verify=verifyssl)
|
verify=verifyssl)
|
||||||
|
|
||||||
##### THE RESPONSE #####
|
##### THE RESPONSE #####
|
||||||
log(r.url, 1)
|
self.logMsg(r.url, 1)
|
||||||
log(r, 1)
|
self.logMsg(r, 1)
|
||||||
|
|
||||||
if r.status_code == 204:
|
if r.status_code == 204:
|
||||||
# No body in the response
|
# No body in the response
|
||||||
log("====== 204 Success ======", 1)
|
self.logMsg("====== 204 Success ======", 1)
|
||||||
|
|
||||||
elif r.status_code == requests.codes.ok:
|
elif r.status_code == requests.codes.ok:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# UNICODE - JSON object
|
# UNICODE - JSON object
|
||||||
r = r.json()
|
r = r.json()
|
||||||
log("====== 200 Success ======", 1)
|
self.logMsg("====== 200 Success ======", 1)
|
||||||
log("Response: %s" % r, 1)
|
self.logMsg("Response: %s" % r, 1)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
except:
|
except:
|
||||||
if r.headers.get('content-type') != "text/html":
|
if r.headers.get('content-type') != "text/html":
|
||||||
log("Unable to convert the response for: %s" % url, 1)
|
self.logMsg("Unable to convert the response for: %s" % url, 1)
|
||||||
else:
|
else:
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
##### EXCEPTIONS #####
|
##### EXCEPTIONS #####
|
||||||
|
|
||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
@ -242,8 +238,8 @@ class ConnectUtils():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
except requests.exceptions.ConnectTimeout as e:
|
except requests.exceptions.ConnectTimeout as e:
|
||||||
log("Server timeout at: %s" % url, 0)
|
self.logMsg("Server timeout at: %s" % url, 0)
|
||||||
log(e, 1)
|
self.logMsg(e, 1)
|
||||||
|
|
||||||
except requests.exceptions.HTTPError as e:
|
except requests.exceptions.HTTPError as e:
|
||||||
|
|
||||||
|
@ -259,11 +255,11 @@ class ConnectUtils():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
except requests.exceptions.SSLError as e:
|
except requests.exceptions.SSLError as e:
|
||||||
log("Invalid SSL certificate for: %s" % url, 0)
|
self.logMsg("Invalid SSL certificate for: %s" % url, 0)
|
||||||
log(e, 1)
|
self.logMsg(e, 1)
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
log("Unknown error connecting to: %s" % url, 0)
|
self.logMsg("Unknown error connecting to: %s" % url, 0)
|
||||||
log(e, 1)
|
self.logMsg(e, 1)
|
||||||
|
|
||||||
return default_link
|
return default_link
|
||||||
|
|
|
@ -30,8 +30,6 @@ class DownloadUtils():
|
||||||
# Borg - multiple instances, shared state
|
# Borg - multiple instances, shared state
|
||||||
_shared_state = {}
|
_shared_state = {}
|
||||||
|
|
||||||
# Requests session
|
|
||||||
timeout = 30
|
|
||||||
# How many failed attempts before declaring PMS dead?
|
# How many failed attempts before declaring PMS dead?
|
||||||
connectionAttempts = 2
|
connectionAttempts = 2
|
||||||
# How many 401 returns before declaring unauthorized?
|
# How many 401 returns before declaring unauthorized?
|
||||||
|
@ -39,6 +37,8 @@ class DownloadUtils():
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.__dict__ = self._shared_state
|
self.__dict__ = self._shared_state
|
||||||
|
# Requests session
|
||||||
|
self.timeout = 30.0
|
||||||
|
|
||||||
def setUsername(self, username):
|
def setUsername(self, username):
|
||||||
"""
|
"""
|
||||||
|
@ -142,21 +142,22 @@ class DownloadUtils():
|
||||||
header.update(options)
|
header.update(options)
|
||||||
return header
|
return header
|
||||||
|
|
||||||
def __doDownload(self, s, type, **kwargs):
|
def __doDownload(self, s, action_type, **kwargs):
|
||||||
if type == "GET":
|
if action_type == "GET":
|
||||||
r = s.get(**kwargs)
|
r = s.get(**kwargs)
|
||||||
elif type == "POST":
|
elif action_type == "POST":
|
||||||
r = s.post(**kwargs)
|
r = s.post(**kwargs)
|
||||||
elif type == "DELETE":
|
elif action_type == "DELETE":
|
||||||
r = s.delete(**kwargs)
|
r = s.delete(**kwargs)
|
||||||
elif type == "OPTIONS":
|
elif action_type == "OPTIONS":
|
||||||
r = s.options(**kwargs)
|
r = s.options(**kwargs)
|
||||||
elif type == "PUT":
|
elif action_type == "PUT":
|
||||||
r = s.put(**kwargs)
|
r = s.put(**kwargs)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def downloadUrl(self, url, type="GET", postBody=None, parameters=None,
|
def downloadUrl(self, url, action_type="GET", postBody=None,
|
||||||
authenticate=True, headerOptions=None, verifySSL=True):
|
parameters=None, authenticate=True, headerOptions=None,
|
||||||
|
verifySSL=True, timeout=None):
|
||||||
"""
|
"""
|
||||||
Override SSL check with verifySSL=False
|
Override SSL check with verifySSL=False
|
||||||
|
|
||||||
|
@ -164,14 +165,14 @@ class DownloadUtils():
|
||||||
Otherwise, 'empty' request will be made
|
Otherwise, 'empty' request will be made
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
False If an error occured
|
None If an error occured
|
||||||
True If connection worked but no body was received
|
True If connection worked but no body was received
|
||||||
401, ... integer if PMS answered with HTTP error 401
|
401, ... integer if PMS answered with HTTP error 401
|
||||||
(unauthorized) or other http error codes
|
(unauthorized) or other http error codes
|
||||||
xml xml etree root object, if applicable
|
xml xml etree root object, if applicable
|
||||||
JSON json() object, if applicable
|
JSON json() object, if applicable
|
||||||
"""
|
"""
|
||||||
kwargs = {}
|
kwargs = {'timeout': self.timeout}
|
||||||
if authenticate is True:
|
if authenticate is True:
|
||||||
# Get requests session
|
# Get requests session
|
||||||
try:
|
try:
|
||||||
|
@ -187,7 +188,6 @@ class DownloadUtils():
|
||||||
# plex.tv and to check for PMS servers
|
# plex.tv and to check for PMS servers
|
||||||
s = requests
|
s = requests
|
||||||
headerOptions = self.getHeader(options=headerOptions)
|
headerOptions = self.getHeader(options=headerOptions)
|
||||||
kwargs['timeout'] = self.timeout
|
|
||||||
if settings('sslcert') != 'None':
|
if settings('sslcert') != 'None':
|
||||||
kwargs['cert'] = settings('sslcert')
|
kwargs['cert'] = settings('sslcert')
|
||||||
|
|
||||||
|
@ -202,10 +202,12 @@ class DownloadUtils():
|
||||||
kwargs['data'] = postBody
|
kwargs['data'] = postBody
|
||||||
if parameters is not None:
|
if parameters is not None:
|
||||||
kwargs['params'] = parameters
|
kwargs['params'] = parameters
|
||||||
|
if timeout is not None:
|
||||||
|
kwargs['timeout'] = timeout
|
||||||
|
|
||||||
# ACTUAL DOWNLOAD HAPPENING HERE
|
# ACTUAL DOWNLOAD HAPPENING HERE
|
||||||
try:
|
try:
|
||||||
r = self.__doDownload(s, type, **kwargs)
|
r = self.__doDownload(s, action_type, **kwargs)
|
||||||
|
|
||||||
# THE EXCEPTIONS
|
# THE EXCEPTIONS
|
||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
@ -252,6 +254,9 @@ class DownloadUtils():
|
||||||
|
|
||||||
if r.status_code == 204:
|
if r.status_code == 204:
|
||||||
# No body in the response
|
# No body in the response
|
||||||
|
# But read (empty) content to release connection back to pool
|
||||||
|
# (see requests: keep-alive documentation)
|
||||||
|
r.content
|
||||||
return True
|
return True
|
||||||
|
|
||||||
elif r.status_code == 401:
|
elif r.status_code == 401:
|
||||||
|
@ -269,11 +274,11 @@ class DownloadUtils():
|
||||||
self.unauthorizedAttempts):
|
self.unauthorizedAttempts):
|
||||||
self.logMsg('We seem to be truly unauthorized for PMS'
|
self.logMsg('We seem to be truly unauthorized for PMS'
|
||||||
' %s ' % url, -1)
|
' %s ' % url, -1)
|
||||||
if window('emby_serverStatus') not in ('401', 'Auth'):
|
if window('plex_serverStatus') not in ('401', 'Auth'):
|
||||||
# Tell userclient token has been revoked.
|
# Tell userclient token has been revoked.
|
||||||
self.logMsg('Setting PMS server status to '
|
self.logMsg('Setting PMS server status to '
|
||||||
'unauthorized', 0)
|
'unauthorized', 0)
|
||||||
window('emby_serverStatus', value="401")
|
window('plex_serverStatus', value="401")
|
||||||
xbmcgui.Dialog().notification(
|
xbmcgui.Dialog().notification(
|
||||||
self.addonName,
|
self.addonName,
|
||||||
"Unauthorized for PMS",
|
"Unauthorized for PMS",
|
||||||
|
@ -328,7 +333,7 @@ class DownloadUtils():
|
||||||
if int(window('countError')) >= self.connectionAttempts:
|
if int(window('countError')) >= self.connectionAttempts:
|
||||||
self.logMsg('Failed to connect to %s too many times. '
|
self.logMsg('Failed to connect to %s too many times. '
|
||||||
'Declare PMS dead' % url, -1)
|
'Declare PMS dead' % url, -1)
|
||||||
window('emby_online', value="false")
|
window('plex_online', value="false")
|
||||||
except:
|
except:
|
||||||
# 'countError' not yet set
|
# 'countError' not yet set
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -34,7 +34,6 @@ class Embydb_Functions():
|
||||||
|
|
||||||
def getViews(self):
|
def getViews(self):
|
||||||
|
|
||||||
embycursor = self.embycursor
|
|
||||||
views = []
|
views = []
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
@ -42,8 +41,8 @@ class Embydb_Functions():
|
||||||
"SELECT view_id",
|
"SELECT view_id",
|
||||||
"FROM view"
|
"FROM view"
|
||||||
))
|
))
|
||||||
embycursor.execute(query)
|
self.embycursor.execute(query)
|
||||||
rows = embycursor.fetchall()
|
rows = self.embycursor.fetchall()
|
||||||
for row in rows:
|
for row in rows:
|
||||||
views.append(row[0])
|
views.append(row[0])
|
||||||
return views
|
return views
|
||||||
|
@ -68,7 +67,6 @@ class Embydb_Functions():
|
||||||
|
|
||||||
def getView_byId(self, viewid):
|
def getView_byId(self, viewid):
|
||||||
|
|
||||||
embycursor = self.embycursor
|
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
|
@ -76,13 +74,13 @@ class Embydb_Functions():
|
||||||
"FROM view",
|
"FROM view",
|
||||||
"WHERE view_id = ?"
|
"WHERE view_id = ?"
|
||||||
))
|
))
|
||||||
embycursor.execute(query, (viewid,))
|
self.embycursor.execute(query, (viewid,))
|
||||||
view = embycursor.fetchone()
|
view = self.embycursor.fetchone()
|
||||||
|
|
||||||
return view
|
return view
|
||||||
|
|
||||||
def getView_byType(self, mediatype):
|
def getView_byType(self, mediatype):
|
||||||
|
|
||||||
embycursor = self.embycursor
|
|
||||||
views = []
|
views = []
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
@ -91,8 +89,8 @@ class Embydb_Functions():
|
||||||
"FROM view",
|
"FROM view",
|
||||||
"WHERE media_type = ?"
|
"WHERE media_type = ?"
|
||||||
))
|
))
|
||||||
embycursor.execute(query, (mediatype,))
|
self.embycursor.execute(query, (mediatype,))
|
||||||
rows = embycursor.fetchall()
|
rows = self.embycursor.fetchall()
|
||||||
for row in rows:
|
for row in rows:
|
||||||
views.append({
|
views.append({
|
||||||
|
|
||||||
|
@ -105,23 +103,22 @@ class Embydb_Functions():
|
||||||
|
|
||||||
def getView_byName(self, tagname):
|
def getView_byName(self, tagname):
|
||||||
|
|
||||||
embycursor = self.embycursor
|
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
"SELECT view_id",
|
"SELECT view_id",
|
||||||
"FROM view",
|
"FROM view",
|
||||||
"WHERE view_name = ?"
|
"WHERE view_name = ?"
|
||||||
))
|
))
|
||||||
embycursor.execute(query, (tagname,))
|
self.embycursor.execute(query, (tagname,))
|
||||||
try:
|
try:
|
||||||
view = embycursor.fetchone()[0]
|
view = self.embycursor.fetchone()[0]
|
||||||
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
view = None
|
view = None
|
||||||
|
|
||||||
return view
|
return view
|
||||||
|
|
||||||
def addView(self, embyid, name, mediatype, tagid):
|
def addView(self, plexid, name, mediatype, tagid):
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
'''
|
'''
|
||||||
|
@ -131,7 +128,7 @@ class Embydb_Functions():
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
self.embycursor.execute(query, (embyid, name, mediatype, tagid))
|
self.embycursor.execute(query, (plexid, name, mediatype, tagid))
|
||||||
|
|
||||||
def updateView(self, name, tagid, mediafolderid):
|
def updateView(self, name, tagid, mediafolderid):
|
||||||
|
|
||||||
|
@ -188,9 +185,7 @@ class Embydb_Functions():
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getItem_byId(self, embyid):
|
def getItem_byId(self, plexid):
|
||||||
|
|
||||||
embycursor = self.embycursor
|
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
|
@ -199,14 +194,12 @@ class Embydb_Functions():
|
||||||
"WHERE emby_id = ?"
|
"WHERE emby_id = ?"
|
||||||
))
|
))
|
||||||
try:
|
try:
|
||||||
embycursor.execute(query, (embyid,))
|
self.embycursor.execute(query, (plexid,))
|
||||||
item = embycursor.fetchone()
|
item = self.embycursor.fetchone()
|
||||||
return item
|
return item
|
||||||
except: return None
|
except: return None
|
||||||
|
|
||||||
def getItem_byWildId(self, embyid):
|
def getItem_byWildId(self, plexid):
|
||||||
|
|
||||||
embycursor = self.embycursor
|
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
|
@ -214,25 +207,19 @@ class Embydb_Functions():
|
||||||
"FROM emby",
|
"FROM emby",
|
||||||
"WHERE emby_id LIKE ?"
|
"WHERE emby_id LIKE ?"
|
||||||
))
|
))
|
||||||
embycursor.execute(query, (embyid+"%",))
|
self.embycursor.execute(query, (plexid+"%",))
|
||||||
items = embycursor.fetchall()
|
return self.embycursor.fetchall()
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getItem_byView(self, mediafolderid):
|
def getItem_byView(self, mediafolderid):
|
||||||
|
|
||||||
embycursor = self.embycursor
|
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
"SELECT kodi_id",
|
"SELECT kodi_id",
|
||||||
"FROM emby",
|
"FROM emby",
|
||||||
"WHERE media_folder = ?"
|
"WHERE media_folder = ?"
|
||||||
))
|
))
|
||||||
embycursor.execute(query, (mediafolderid,))
|
self.embycursor.execute(query, (mediafolderid,))
|
||||||
items = embycursor.fetchall()
|
return self.embycursor.fetchall()
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getPlexId(self, kodiid, mediatype):
|
def getPlexId(self, kodiid, mediatype):
|
||||||
"""
|
"""
|
||||||
|
@ -253,8 +240,6 @@ class Embydb_Functions():
|
||||||
|
|
||||||
def getItem_byKodiId(self, kodiid, mediatype):
|
def getItem_byKodiId(self, kodiid, mediatype):
|
||||||
|
|
||||||
embycursor = self.embycursor
|
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
"SELECT emby_id, parent_id",
|
"SELECT emby_id, parent_id",
|
||||||
|
@ -262,15 +247,11 @@ class Embydb_Functions():
|
||||||
"WHERE kodi_id = ?",
|
"WHERE kodi_id = ?",
|
||||||
"AND media_type = ?"
|
"AND media_type = ?"
|
||||||
))
|
))
|
||||||
embycursor.execute(query, (kodiid, mediatype,))
|
self.embycursor.execute(query, (kodiid, mediatype,))
|
||||||
item = embycursor.fetchone()
|
return self.embycursor.fetchone()
|
||||||
|
|
||||||
return item
|
|
||||||
|
|
||||||
def getItem_byParentId(self, parentid, mediatype):
|
def getItem_byParentId(self, parentid, mediatype):
|
||||||
|
|
||||||
embycursor = self.embycursor
|
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
"SELECT emby_id, kodi_id, kodi_fileid",
|
"SELECT emby_id, kodi_id, kodi_fileid",
|
||||||
|
@ -278,15 +259,11 @@ class Embydb_Functions():
|
||||||
"WHERE parent_id = ?",
|
"WHERE parent_id = ?",
|
||||||
"AND media_type = ?"
|
"AND media_type = ?"
|
||||||
))
|
))
|
||||||
embycursor.execute(query, (parentid, mediatype,))
|
self.embycursor.execute(query, (parentid, mediatype,))
|
||||||
items = embycursor.fetchall()
|
return self.embycursor.fetchall()
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getItemId_byParentId(self, parentid, mediatype):
|
def getItemId_byParentId(self, parentid, mediatype):
|
||||||
|
|
||||||
embycursor = self.embycursor
|
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
"SELECT emby_id, kodi_id",
|
"SELECT emby_id, kodi_id",
|
||||||
|
@ -294,29 +271,21 @@ class Embydb_Functions():
|
||||||
"WHERE parent_id = ?",
|
"WHERE parent_id = ?",
|
||||||
"AND media_type = ?"
|
"AND media_type = ?"
|
||||||
))
|
))
|
||||||
embycursor.execute(query, (parentid, mediatype,))
|
self.embycursor.execute(query, (parentid, mediatype,))
|
||||||
items = embycursor.fetchall()
|
return self.embycursor.fetchall()
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getChecksum(self, mediatype):
|
def getChecksum(self, mediatype):
|
||||||
|
|
||||||
embycursor = self.embycursor
|
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
"SELECT emby_id, checksum",
|
"SELECT emby_id, checksum",
|
||||||
"FROM emby",
|
"FROM emby",
|
||||||
"WHERE emby_type = ?"
|
"WHERE emby_type = ?"
|
||||||
))
|
))
|
||||||
embycursor.execute(query, (mediatype,))
|
self.embycursor.execute(query, (mediatype,))
|
||||||
items = embycursor.fetchall()
|
return self.embycursor.fetchall()
|
||||||
|
|
||||||
return items
|
def getMediaType_byId(self, plexid):
|
||||||
|
|
||||||
def getMediaType_byId(self, embyid):
|
|
||||||
|
|
||||||
embycursor = self.embycursor
|
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
|
@ -324,9 +293,10 @@ class Embydb_Functions():
|
||||||
"FROM emby",
|
"FROM emby",
|
||||||
"WHERE emby_id = ?"
|
"WHERE emby_id = ?"
|
||||||
))
|
))
|
||||||
embycursor.execute(query, (embyid,))
|
self.embycursor.execute(query, (plexid,))
|
||||||
try:
|
try:
|
||||||
itemtype = embycursor.fetchone()[0]
|
itemtype = self.embycursor.fetchone()[0]
|
||||||
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
itemtype = None
|
itemtype = None
|
||||||
|
|
||||||
|
@ -345,7 +315,7 @@ class Embydb_Functions():
|
||||||
|
|
||||||
return sorted_items
|
return sorted_items
|
||||||
|
|
||||||
def addReference(self, embyid, kodiid, embytype, mediatype, fileid=None, pathid=None,
|
def addReference(self, plexid, kodiid, embytype, mediatype, fileid=None, pathid=None,
|
||||||
parentid=None, checksum=None, mediafolderid=None):
|
parentid=None, checksum=None, mediafolderid=None):
|
||||||
query = (
|
query = (
|
||||||
'''
|
'''
|
||||||
|
@ -356,18 +326,18 @@ class Embydb_Functions():
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype,
|
self.embycursor.execute(query, (plexid, kodiid, fileid, pathid, embytype, mediatype,
|
||||||
parentid, checksum, mediafolderid))
|
parentid, checksum, mediafolderid))
|
||||||
|
|
||||||
def updateReference(self, embyid, checksum):
|
def updateReference(self, plexid, checksum):
|
||||||
|
|
||||||
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
|
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
|
||||||
self.embycursor.execute(query, (checksum, embyid))
|
self.embycursor.execute(query, (checksum, plexid))
|
||||||
|
|
||||||
def updateParentId(self, embyid, parent_kodiid):
|
def updateParentId(self, plexid, parent_kodiid):
|
||||||
|
|
||||||
query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?"
|
query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?"
|
||||||
self.embycursor.execute(query, (parent_kodiid, embyid))
|
self.embycursor.execute(query, (parent_kodiid, plexid))
|
||||||
|
|
||||||
def removeItems_byParentId(self, parent_kodiid, mediatype):
|
def removeItems_byParentId(self, parent_kodiid, mediatype):
|
||||||
|
|
||||||
|
@ -389,13 +359,13 @@ class Embydb_Functions():
|
||||||
))
|
))
|
||||||
self.embycursor.execute(query, (kodiid, mediatype,))
|
self.embycursor.execute(query, (kodiid, mediatype,))
|
||||||
|
|
||||||
def removeItem(self, embyid):
|
def removeItem(self, plexid):
|
||||||
|
|
||||||
query = "DELETE FROM emby WHERE emby_id = ?"
|
query = "DELETE FROM emby WHERE emby_id = ?"
|
||||||
self.embycursor.execute(query, (embyid,))
|
self.embycursor.execute(query, (plexid,))
|
||||||
|
|
||||||
def removeWildItem(self, embyid):
|
def removeWildItem(self, plexid):
|
||||||
|
|
||||||
query = "DELETE FROM emby WHERE emby_id LIKE ?"
|
query = "DELETE FROM emby WHERE emby_id LIKE ?"
|
||||||
self.embycursor.execute(query, (embyid+"%",))
|
self.embycursor.execute(query, (plexid+"%",))
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,6 +12,7 @@ import downloadutils
|
||||||
import userclient
|
import userclient
|
||||||
|
|
||||||
import PlexAPI
|
import PlexAPI
|
||||||
|
from PlexFunctions import GetMachineIdentifier
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -20,129 +21,271 @@ import PlexAPI
|
||||||
class InitialSetup():
|
class InitialSetup():
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.logMsg('Entering initialsetup class', 1)
|
||||||
self.clientInfo = clientinfo.ClientInfo()
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
self.addonId = self.clientInfo.getAddonId()
|
self.addonId = self.clientInfo.getAddonId()
|
||||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||||
self.userClient = userclient.UserClient()
|
self.userClient = userclient.UserClient()
|
||||||
self.plx = PlexAPI.PlexAPI()
|
self.plx = PlexAPI.PlexAPI()
|
||||||
|
self.dialog = xbmcgui.Dialog()
|
||||||
|
|
||||||
def setup(self, forcePlexTV=False, chooseServer=False):
|
self.string = xbmcaddon.Addon().getLocalizedString
|
||||||
"""
|
|
||||||
Initial setup. Run once upon startup.
|
self.server = self.userClient.getServer()
|
||||||
Check server, user, direct paths, music, direct stream if not direct
|
self.serverid = utils.settings('plex_machineIdentifier')
|
||||||
path.
|
|
||||||
"""
|
|
||||||
string = xbmcaddon.Addon().getLocalizedString
|
|
||||||
# SERVER INFO #####
|
|
||||||
self.logMsg("Initial setup called.", 0)
|
|
||||||
server = self.userClient.getServer()
|
|
||||||
serverid = utils.settings('plex_machineIdentifier')
|
|
||||||
# Get Plex credentials from settings file, if they exist
|
# Get Plex credentials from settings file, if they exist
|
||||||
plexdict = self.plx.GetPlexLoginFromSettings()
|
plexdict = self.plx.GetPlexLoginFromSettings()
|
||||||
myplexlogin = plexdict['myplexlogin']
|
self.myplexlogin = plexdict['myplexlogin'] == 'true'
|
||||||
plexLogin = plexdict['plexLogin']
|
self.plexLogin = plexdict['plexLogin']
|
||||||
plexToken = plexdict['plexToken']
|
self.plexToken = plexdict['plexToken']
|
||||||
plexid = plexdict['plexid']
|
self.plexid = plexdict['plexid']
|
||||||
if plexToken:
|
if self.plexToken:
|
||||||
self.logMsg('Found plex.tv token in settings', 0)
|
self.logMsg('Found a plex.tv token in the settings', 1)
|
||||||
|
|
||||||
dialog = xbmcgui.Dialog()
|
def PlexTVSignIn(self):
|
||||||
|
"""
|
||||||
|
Signs (freshly) in to plex.tv (will be saved to file settings)
|
||||||
|
|
||||||
# Optionally sign into plex.tv. Will not be called on very first run
|
Returns True if successful, or False if not
|
||||||
# as plexToken will be ''
|
"""
|
||||||
if (plexToken and myplexlogin == 'true' and forcePlexTV is False
|
result = self.plx.PlexTvSignInWithPin()
|
||||||
and chooseServer is False):
|
if result:
|
||||||
chk = self.plx.CheckConnection('plex.tv', plexToken)
|
self.plexLogin = result['username']
|
||||||
try:
|
self.plexToken = result['token']
|
||||||
chk.attrib
|
self.plexid = result['plexid']
|
||||||
except:
|
return True
|
||||||
pass
|
return False
|
||||||
else:
|
|
||||||
# Success - we downloaded an xml!
|
def CheckPlexTVSignIn(self):
|
||||||
chk = 200
|
"""
|
||||||
|
Checks existing connection to plex.tv. If not, triggers sign in
|
||||||
|
|
||||||
|
Returns True if signed in, False otherwise
|
||||||
|
"""
|
||||||
|
answer = True
|
||||||
|
chk = self.plx.CheckConnection('plex.tv', token=self.plexToken)
|
||||||
|
if chk in (401, 403):
|
||||||
# HTTP Error: unauthorized. Token is no longer valid
|
# HTTP Error: unauthorized. Token is no longer valid
|
||||||
if chk in (401, 403):
|
self.logMsg('plex.tv connection returned HTTP %s' % str(chk), 1)
|
||||||
self.logMsg('plex.tv connection returned HTTP %s' % chk, 0)
|
# Delete token in the settings
|
||||||
# Delete token in the settings
|
utils.settings('plexToken', value='')
|
||||||
utils.settings('plexToken', value='')
|
utils.settings('plexLogin', value='')
|
||||||
# Could not login, please try again
|
# Could not login, please try again
|
||||||
dialog.ok(self.addonName,
|
self.dialog.ok(self.addonName,
|
||||||
string(39009))
|
self.string(39009))
|
||||||
result = self.plx.PlexTvSignInWithPin()
|
answer = self.PlexTVSignIn()
|
||||||
if result:
|
elif chk is False or chk >= 400:
|
||||||
plexLogin = result['username']
|
# Problems connecting to plex.tv. Network or internet issue?
|
||||||
plexToken = result['token']
|
self.logMsg('Problems connecting to plex.tv; connection returned '
|
||||||
plexid = result['plexid']
|
'HTTP %s' % str(chk), 1)
|
||||||
elif chk is False or chk >= 400:
|
self.dialog.ok(self.addonName,
|
||||||
# Problems connecting to plex.tv. Network or internet issue?
|
self.string(39010))
|
||||||
self.logMsg('plex.tv connection returned HTTP %s'
|
answer = False
|
||||||
% str(chk), 0)
|
else:
|
||||||
dialog.ok(self.addonName,
|
self.logMsg('plex.tv connection with token successful', 1)
|
||||||
string(39010))
|
utils.settings('plex_status', value='Logged in to plex.tv')
|
||||||
|
# Refresh the info from Plex.tv
|
||||||
|
xml = self.doUtils('https://plex.tv/users/account',
|
||||||
|
authenticate=False,
|
||||||
|
headerOptions={'X-Plex-Token': self.plexToken})
|
||||||
|
try:
|
||||||
|
self.plexLogin = xml.attrib['title']
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
self.logMsg('Failed to update Plex info from plex.tv', -1)
|
||||||
else:
|
else:
|
||||||
self.logMsg('plex.tv connection with token successful', 0)
|
utils.settings('plexLogin', value=self.plexLogin)
|
||||||
# Refresh the info from Plex.tv
|
home = 'true' if xml.attrib.get('home') == '1' else 'false'
|
||||||
xml = self.doUtils('https://plex.tv/users/account',
|
utils.settings('plexhome', value=home)
|
||||||
authenticate=False,
|
utils.settings('plexAvatar', value=xml.attrib.get('thumb'))
|
||||||
headerOptions={'X-Plex-Token': plexToken})
|
utils.settings(
|
||||||
try:
|
'plexHomeSize', value=xml.attrib.get('homeSize', '1'))
|
||||||
xml.attrib
|
self.logMsg('Updated Plex info from plex.tv', 1)
|
||||||
except:
|
return answer
|
||||||
self.logMsg('Failed to update Plex info from plex.tv', -1)
|
|
||||||
|
def CheckPMS(self):
|
||||||
|
"""
|
||||||
|
Check the PMS that was set in file settings.
|
||||||
|
Will return False if we need to reconnect, because:
|
||||||
|
PMS could not be reached (no matter the authorization)
|
||||||
|
machineIdentifier did not match
|
||||||
|
|
||||||
|
Will also set the PMS machineIdentifier in the file settings if it was
|
||||||
|
not set before
|
||||||
|
"""
|
||||||
|
answer = True
|
||||||
|
chk = self.plx.CheckConnection(self.server,
|
||||||
|
verifySSL=False)
|
||||||
|
if chk is False:
|
||||||
|
self.logMsg('Could not reach PMS %s' % self.server, -1)
|
||||||
|
answer = False
|
||||||
|
if answer is True and not self.serverid:
|
||||||
|
self.logMsg('No PMS machineIdentifier found for %s. Trying to '
|
||||||
|
'get the PMS unique ID' % self.server, 1)
|
||||||
|
self.serverid = GetMachineIdentifier(self.server)
|
||||||
|
if self.serverid is None:
|
||||||
|
self.logMsg('Could not retrieve machineIdentifier', -1)
|
||||||
|
answer = False
|
||||||
|
else:
|
||||||
|
utils.settings('plex_machineIdentifier', value=self.serverid)
|
||||||
|
elif answer is True:
|
||||||
|
tempServerid = GetMachineIdentifier(self.server)
|
||||||
|
if tempServerid != self.serverid:
|
||||||
|
self.logMsg('The current PMS %s was expected to have a '
|
||||||
|
'unique machineIdentifier of %s. But we got '
|
||||||
|
'%s. Pick a new server to be sure'
|
||||||
|
% (self.server, self.serverid, tempServerid), 1)
|
||||||
|
answer = False
|
||||||
|
return answer
|
||||||
|
|
||||||
|
def _getServerList(self):
|
||||||
|
"""
|
||||||
|
Returns a list of servers from GDM and possibly plex.tv
|
||||||
|
"""
|
||||||
|
self.plx.discoverPMS(xbmc.getIPAddress(),
|
||||||
|
plexToken=self.plexToken)
|
||||||
|
serverlist = self.plx.returnServerList(self.plx.g_PMS)
|
||||||
|
self.logMsg('PMS serverlist: %s' % serverlist, 2)
|
||||||
|
return serverlist
|
||||||
|
|
||||||
|
def _checkServerCon(self, server):
|
||||||
|
"""
|
||||||
|
Checks for server's connectivity. Returns CheckConnection result
|
||||||
|
"""
|
||||||
|
# Re-direct via plex if remote - will lead to the correct SSL
|
||||||
|
# certificate
|
||||||
|
if server['local'] == '1':
|
||||||
|
url = '%s://%s:%s' \
|
||||||
|
% (server['scheme'], server['ip'], server['port'])
|
||||||
|
# Deactive SSL verification if the server is local!
|
||||||
|
verifySSL = False
|
||||||
|
else:
|
||||||
|
url = server['baseURL']
|
||||||
|
verifySSL = None
|
||||||
|
chk = self.plx.CheckConnection(url,
|
||||||
|
token=server['accesstoken'],
|
||||||
|
verifySSL=verifySSL)
|
||||||
|
return chk
|
||||||
|
|
||||||
|
def PickPMS(self, showDialog=False):
|
||||||
|
"""
|
||||||
|
Searches for PMS in local Lan and optionally (if self.plexToken set)
|
||||||
|
also on plex.tv
|
||||||
|
showDialog=True: let the user pick one
|
||||||
|
showDialog=False: automatically pick PMS based on machineIdentifier
|
||||||
|
|
||||||
|
Returns the picked PMS' detail as a dict:
|
||||||
|
{
|
||||||
|
'name': friendlyName, the Plex server's name
|
||||||
|
'address': ip:port
|
||||||
|
'ip': ip, without http/https
|
||||||
|
'port': port
|
||||||
|
'scheme': 'http'/'https', nice for checking for secure connections
|
||||||
|
'local': '1'/'0', Is the server a local server?
|
||||||
|
'owned': '1'/'0', Is the server owned by the user?
|
||||||
|
'machineIdentifier': id, Plex server machine identifier
|
||||||
|
'accesstoken': token Access token to this server
|
||||||
|
'baseURL': baseURL scheme://ip:port
|
||||||
|
'ownername' Plex username of PMS owner
|
||||||
|
}
|
||||||
|
|
||||||
|
or None if unsuccessful
|
||||||
|
"""
|
||||||
|
server = None
|
||||||
|
# If no server is set, let user choose one
|
||||||
|
if not self.server or not self.serverid:
|
||||||
|
showDialog = True
|
||||||
|
if showDialog is True:
|
||||||
|
server = self._UserPickPMS()
|
||||||
|
else:
|
||||||
|
server = self._AutoPickPMS()
|
||||||
|
return server
|
||||||
|
|
||||||
|
def _AutoPickPMS(self):
|
||||||
|
"""
|
||||||
|
Will try to pick PMS based on machineIdentifier saved in file settings
|
||||||
|
but only once
|
||||||
|
|
||||||
|
Returns server or None if unsuccessful
|
||||||
|
"""
|
||||||
|
httpsUpdated = False
|
||||||
|
checkedPlexTV = False
|
||||||
|
server = None
|
||||||
|
while True:
|
||||||
|
if httpsUpdated is False:
|
||||||
|
serverlist = self._getServerList()
|
||||||
|
for item in serverlist:
|
||||||
|
if item.get('machineIdentifier') == self.serverid:
|
||||||
|
server = item
|
||||||
|
if server is None:
|
||||||
|
name = utils.settings('plex_servername')
|
||||||
|
self.logMsg('The PMS you have used before with a unique '
|
||||||
|
'machineIdentifier of %s and name %s is '
|
||||||
|
'offline' % (self.serverid, name), -1)
|
||||||
|
# "PMS xyz offline"
|
||||||
|
self.dialog.notification(self.addonName,
|
||||||
|
'%s %s'
|
||||||
|
% (name, self.string(39213)),
|
||||||
|
xbmcgui.NOTIFICATION_ERROR,
|
||||||
|
7000,
|
||||||
|
False)
|
||||||
|
return
|
||||||
|
chk = self._checkServerCon(server)
|
||||||
|
if chk == 504 and httpsUpdated is False:
|
||||||
|
# Not able to use HTTP, try HTTPs for now
|
||||||
|
server['scheme'] = 'https'
|
||||||
|
httpsUpdated = True
|
||||||
|
continue
|
||||||
|
if chk == 401:
|
||||||
|
self.logMsg('Not yet authorized for Plex server %s'
|
||||||
|
% server['name'], -1)
|
||||||
|
if self.CheckPlexTVSignIn() is True:
|
||||||
|
if checkedPlexTV is False:
|
||||||
|
# Try again
|
||||||
|
checkedPlexTV = True
|
||||||
|
httpsUpdated = False
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
self.logMsg('Not authorized even though we are signed '
|
||||||
|
' in to plex.tv correctly', -1)
|
||||||
|
self.dialog.ok(self.addonName, '%s %s'
|
||||||
|
% self.string(39214) + server['name'])
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
plexLogin = xml.attrib.get('title')
|
return
|
||||||
utils.settings('plexLogin', value=plexLogin)
|
# Problems connecting
|
||||||
home = 'true' if xml.attrib.get('home') == '1' else 'false'
|
elif chk >= 400 or chk is False:
|
||||||
utils.settings('plexhome', value=home)
|
self.logMsg('Problems connecting to server %s. chk is %s'
|
||||||
utils.settings('plexAvatar', value=xml.attrib.get('thumb'))
|
% (server['name'], chk), -1)
|
||||||
utils.settings(
|
return
|
||||||
'plexHomeSize', value=xml.attrib.get('homeSize', '1'))
|
self.logMsg('We found a server to automatically connect to: %s'
|
||||||
self.logMsg('Updated Plex info from plex.tv', 0)
|
% server['name'], 1)
|
||||||
|
return server
|
||||||
|
|
||||||
# If a Plex server IP has already been set, return.
|
def _UserPickPMS(self):
|
||||||
if server and forcePlexTV is False and chooseServer is False:
|
"""
|
||||||
self.logMsg("Server is already set.", 0)
|
Lets user pick his/her PMS from a list
|
||||||
self.logMsg("url: %s, Plex machineIdentifier: %s"
|
|
||||||
% (server, serverid), 0)
|
|
||||||
return
|
|
||||||
|
|
||||||
# If not already retrieved myplex info, optionally let user sign in
|
Returns server or None if unsuccessful
|
||||||
# to plex.tv. This DOES get called on very first install run
|
"""
|
||||||
if ((not plexToken and myplexlogin == 'true' and chooseServer is False)
|
|
||||||
or forcePlexTV):
|
|
||||||
result = self.plx.PlexTvSignInWithPin()
|
|
||||||
if result:
|
|
||||||
plexLogin = result['username']
|
|
||||||
plexToken = result['token']
|
|
||||||
plexid = result['plexid']
|
|
||||||
# Get g_PMS list of servers (saved to plx.g_PMS)
|
|
||||||
httpsUpdated = False
|
httpsUpdated = False
|
||||||
while True:
|
while True:
|
||||||
if httpsUpdated is False:
|
if httpsUpdated is False:
|
||||||
# Populate g_PMS variable with the found Plex servers
|
serverlist = self._getServerList()
|
||||||
self.plx.discoverPMS(xbmc.getIPAddress(),
|
|
||||||
plexToken=plexToken)
|
|
||||||
isconnected = False
|
|
||||||
self.logMsg('g_PMS: %s' % self.plx.g_PMS, 1)
|
|
||||||
serverlist = self.plx.returnServerList(self.plx.g_PMS)
|
|
||||||
self.logMsg('PMS serverlist: %s' % serverlist, 2)
|
|
||||||
# Let user pick server from a list
|
|
||||||
# Get a nicer list
|
|
||||||
dialoglist = []
|
|
||||||
# Exit if no servers found
|
# Exit if no servers found
|
||||||
if len(serverlist) == 0:
|
if len(serverlist) == 0:
|
||||||
dialog.ok(
|
self.logMsg('No plex media servers found!', -1)
|
||||||
self.addonName,
|
self.dialog.ok(self.addonName, self.string(39011))
|
||||||
string(39011)
|
return
|
||||||
)
|
# Get a nicer list
|
||||||
break
|
dialoglist = []
|
||||||
for server in serverlist:
|
for server in serverlist:
|
||||||
if server['local'] == '1':
|
if server['local'] == '1':
|
||||||
# server is in the same network as client. Add "local"
|
# server is in the same network as client.
|
||||||
msg = string(39022)
|
# Add"local"
|
||||||
|
msg = self.string(39022)
|
||||||
else:
|
else:
|
||||||
# Add 'remote'
|
# Add 'remote'
|
||||||
msg = string(39054)
|
msg = self.string(39054)
|
||||||
if server.get('ownername'):
|
if server.get('ownername'):
|
||||||
# Display username if its not our PMS
|
# Display username if its not our PMS
|
||||||
dialoglist.append('%s (%s, %s)'
|
dialoglist.append('%s (%s, %s)'
|
||||||
|
@ -151,33 +294,12 @@ class InitialSetup():
|
||||||
msg))
|
msg))
|
||||||
else:
|
else:
|
||||||
dialoglist.append('%s (%s)'
|
dialoglist.append('%s (%s)'
|
||||||
% (server['name'],
|
% (server['name'], msg))
|
||||||
msg))
|
# Let user pick server from a list
|
||||||
resp = dialog.select(string(39012), dialoglist)
|
resp = self.dialog.select(self.string(39012), dialoglist)
|
||||||
|
|
||||||
server = serverlist[resp]
|
server = serverlist[resp]
|
||||||
activeServer = server['machineIdentifier']
|
chk = self._checkServerCon(server)
|
||||||
# Re-direct via plex if remote - will lead to the correct SSL
|
|
||||||
# certificate
|
|
||||||
if server['local'] == '1':
|
|
||||||
url = server['scheme'] + '://' + server['ip'] + ':' \
|
|
||||||
+ server['port']
|
|
||||||
else:
|
|
||||||
url = server['baseURL']
|
|
||||||
# Deactive SSL verification if the server is local!
|
|
||||||
# Watch out - settings is cached by Kodi - use dedicated var!
|
|
||||||
if server['local'] == '1':
|
|
||||||
utils.settings('sslverify', 'false')
|
|
||||||
self.logMsg("Setting SSL verify to false, because server is "
|
|
||||||
"local", 1)
|
|
||||||
verifySSL = False
|
|
||||||
else:
|
|
||||||
utils.settings('sslverify', 'true')
|
|
||||||
self.logMsg("Setting SSL verify to true, because server is "
|
|
||||||
"not local", 1)
|
|
||||||
verifySSL = None
|
|
||||||
chk = self.plx.CheckConnection(url,
|
|
||||||
server['accesstoken'],
|
|
||||||
verifySSL=verifySSL)
|
|
||||||
if chk == 504 and httpsUpdated is False:
|
if chk == 504 and httpsUpdated is False:
|
||||||
# Not able to use HTTP, try HTTPs for now
|
# Not able to use HTTP, try HTTPs for now
|
||||||
serverlist[resp]['scheme'] = 'https'
|
serverlist[resp]['scheme'] = 'https'
|
||||||
|
@ -185,68 +307,124 @@ class InitialSetup():
|
||||||
continue
|
continue
|
||||||
httpsUpdated = False
|
httpsUpdated = False
|
||||||
if chk == 401:
|
if chk == 401:
|
||||||
# Not yet authorized for Plex server
|
self.logMsg('Not yet authorized for Plex server %s'
|
||||||
|
% server['name'], -1)
|
||||||
# Please sign in to plex.tv
|
# Please sign in to plex.tv
|
||||||
dialog.ok(self.addonName,
|
self.dialog.ok(self.addonName,
|
||||||
string(39013) + server['name'],
|
self.string(39013) + server['name'],
|
||||||
string(39014))
|
self.string(39014))
|
||||||
result = self.plx.PlexTvSignInWithPin()
|
if self.PlexTVSignIn() is False:
|
||||||
if result:
|
|
||||||
plexLogin = result['username']
|
|
||||||
plexToken = result['token']
|
|
||||||
plexid = result['plexid']
|
|
||||||
else:
|
|
||||||
# Exit while loop if user cancels
|
# Exit while loop if user cancels
|
||||||
break
|
return
|
||||||
# Problems connecting
|
# Problems connecting
|
||||||
elif chk >= 400 or chk is False:
|
elif chk >= 400 or chk is False:
|
||||||
# Problems connecting to server. Pick another server?
|
# Problems connecting to server. Pick another server?
|
||||||
resp = dialog.yesno(self.addonName,
|
answ = self.dialog.yesno(self.addonName,
|
||||||
string(39015))
|
self.string(39015))
|
||||||
# Exit while loop if user chooses No
|
# Exit while loop if user chooses No
|
||||||
if not resp:
|
if not answ:
|
||||||
break
|
return
|
||||||
# Otherwise: connection worked!
|
# Otherwise: connection worked!
|
||||||
else:
|
else:
|
||||||
isconnected = True
|
return server
|
||||||
break
|
|
||||||
if not isconnected:
|
def WritePMStoSettings(self, server):
|
||||||
# Enter Kodi settings instead
|
"""
|
||||||
xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId)
|
Saves server to file settings. server is a dict of the form:
|
||||||
return
|
{
|
||||||
# Write to Kodi settings file
|
'name': friendlyName, the Plex server's name
|
||||||
utils.settings('plex_machineIdentifier', activeServer)
|
'address': ip:port
|
||||||
|
'ip': ip, without http/https
|
||||||
|
'port': port
|
||||||
|
'scheme': 'http'/'https', nice for checking for secure connections
|
||||||
|
'local': '1'/'0', Is the server a local server?
|
||||||
|
'owned': '1'/'0', Is the server owned by the user?
|
||||||
|
'machineIdentifier': id, Plex server machine identifier
|
||||||
|
'accesstoken': token Access token to this server
|
||||||
|
'baseURL': baseURL scheme://ip:port
|
||||||
|
'ownername' Plex username of PMS owner
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
utils.settings('plex_machineIdentifier', server['machineIdentifier'])
|
||||||
utils.settings('plex_servername', server['name'])
|
utils.settings('plex_servername', server['name'])
|
||||||
utils.settings('plex_serverowned',
|
utils.settings('plex_serverowned',
|
||||||
'true' if server['owned'] == '1'
|
'true' if server['owned'] == '1'
|
||||||
else 'false')
|
else 'false')
|
||||||
|
# Careful to distinguish local from remote PMS
|
||||||
if server['local'] == '1':
|
if server['local'] == '1':
|
||||||
scheme = server['scheme']
|
scheme = server['scheme']
|
||||||
utils.settings('ipaddress', server['ip'])
|
utils.settings('ipaddress', server['ip'])
|
||||||
utils.settings('port', server['port'])
|
utils.settings('port', server['port'])
|
||||||
|
self.logMsg("Setting SSL verify to false, because server is "
|
||||||
|
"local", 1)
|
||||||
|
utils.settings('sslverify', 'false')
|
||||||
else:
|
else:
|
||||||
baseURL = server['baseURL'].split(':')
|
baseURL = server['baseURL'].split(':')
|
||||||
scheme = baseURL[0]
|
scheme = baseURL[0]
|
||||||
utils.settings('ipaddress', baseURL[1].replace('//', ''))
|
utils.settings('ipaddress', baseURL[1].replace('//', ''))
|
||||||
utils.settings('port', baseURL[2])
|
utils.settings('port', baseURL[2])
|
||||||
|
self.logMsg("Setting SSL verify to true, because server is not "
|
||||||
|
"local", 1)
|
||||||
|
utils.settings('sslverify', 'true')
|
||||||
|
|
||||||
if scheme == 'https':
|
if scheme == 'https':
|
||||||
utils.settings('https', 'true')
|
utils.settings('https', 'true')
|
||||||
else:
|
else:
|
||||||
utils.settings('https', 'false')
|
utils.settings('https', 'false')
|
||||||
|
# And finally do some logging
|
||||||
self.logMsg("Writing to Kodi user settings file", 0)
|
self.logMsg("Writing to Kodi user settings file", 0)
|
||||||
self.logMsg("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s "
|
self.logMsg("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s "
|
||||||
% (activeServer, server['ip'], server['port'],
|
% (server['machineIdentifier'], server['ip'],
|
||||||
server['scheme']), 0)
|
server['port'], server['scheme']), 0)
|
||||||
|
|
||||||
if forcePlexTV is True or chooseServer is True:
|
def setup(self):
|
||||||
|
"""
|
||||||
|
Initial setup. Run once upon startup.
|
||||||
|
|
||||||
|
Check server, user, direct paths, music, direct stream if not direct
|
||||||
|
path.
|
||||||
|
"""
|
||||||
|
self.logMsg("Initial setup called.", 0)
|
||||||
|
dialog = self.dialog
|
||||||
|
string = self.string
|
||||||
|
|
||||||
|
# Optionally sign into plex.tv. Will not be called on very first run
|
||||||
|
# as plexToken will be ''
|
||||||
|
utils.settings('plex_status', value='Not logged in to plex.tv')
|
||||||
|
if self.plexToken and self.myplexlogin:
|
||||||
|
self.CheckPlexTVSignIn()
|
||||||
|
|
||||||
|
# If a Plex server IP has already been set
|
||||||
|
# return only if the right machine identifier is found
|
||||||
|
getNewIP = False
|
||||||
|
if self.server:
|
||||||
|
self.logMsg("PMS is already set: %s. Checking now..."
|
||||||
|
% self.server, 0)
|
||||||
|
getNewIP = not self.CheckPMS()
|
||||||
|
if getNewIP is False:
|
||||||
|
self.logMsg("Using PMS %s with machineIdentifier %s"
|
||||||
|
% (self.server, self.serverid), 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
# If not already retrieved myplex info, optionally let user sign in
|
||||||
|
# to plex.tv. This DOES get called on very first install run
|
||||||
|
if not self.plexToken and self.myplexlogin:
|
||||||
|
self.PlexTVSignIn()
|
||||||
|
|
||||||
|
server = self.PickPMS()
|
||||||
|
if server is not None:
|
||||||
|
# Write our chosen server to Kodi settings file
|
||||||
|
self.WritePMStoSettings(server)
|
||||||
|
|
||||||
|
# User already answered the installation questions
|
||||||
|
if utils.settings('InstallQuestionsAnswered') == 'true':
|
||||||
return
|
return
|
||||||
|
|
||||||
goToSettings = False
|
# Additional settings where the user needs to choose
|
||||||
# Direct paths (\\NAS\mymovie.mkv) or addon (http)?
|
# Direct paths (\\NAS\mymovie.mkv) or addon (http)?
|
||||||
if dialog.yesno(heading=self.addonName,
|
if dialog.yesno(self.addonName,
|
||||||
line1=string(39027),
|
string(39027),
|
||||||
line2=string(39028),
|
string(39028),
|
||||||
nolabel="Addon (Default)",
|
nolabel="Addon (Default)",
|
||||||
yeslabel="Native (Direct Paths)"):
|
yeslabel="Native (Direct Paths)"):
|
||||||
self.logMsg("User opted to use direct paths.", 1)
|
self.logMsg("User opted to use direct paths.", 1)
|
||||||
|
@ -284,15 +462,36 @@ class InitialSetup():
|
||||||
else:
|
else:
|
||||||
utils.advancedSettingsXML()
|
utils.advancedSettingsXML()
|
||||||
|
|
||||||
|
# Download additional art from FanArtTV
|
||||||
|
if dialog.yesno(heading=self.addonName,
|
||||||
|
line1=string(39061)):
|
||||||
|
self.logMsg("User opted to use FanArtTV", 1)
|
||||||
|
utils.settings('FanartTV', value="true")
|
||||||
|
|
||||||
|
# Is your Kodi installed on a low-powered device like a Raspberry Pi?
|
||||||
|
# If yes, then we will reduce the strain on Kodi to prevent it from
|
||||||
|
# crashing.
|
||||||
|
if dialog.yesno(heading=self.addonName,
|
||||||
|
line1=string(39072)):
|
||||||
|
self.logMsg('User thinks that PKC runs on a raspi or similar', 1)
|
||||||
|
utils.settings('imageCacheLimit', value='1')
|
||||||
|
|
||||||
|
# Make sure that we only ask these questions upon first installation
|
||||||
|
utils.settings('InstallQuestionsAnswered', value='true')
|
||||||
|
|
||||||
if goToSettings is False:
|
if goToSettings is False:
|
||||||
# Open Settings page now? You will need to restart!
|
# Open Settings page now? You will need to restart!
|
||||||
goToSettings = dialog.yesno(heading=self.addonName,
|
goToSettings = dialog.yesno(heading=self.addonName,
|
||||||
line1=string(39017))
|
line1=string(39017))
|
||||||
if goToSettings:
|
if goToSettings:
|
||||||
utils.window('emby_serverStatus', value="Stop")
|
utils.window('plex_serverStatus', value="Stop")
|
||||||
xbmc.executebuiltin(
|
xbmc.executebuiltin(
|
||||||
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||||
else:
|
else:
|
||||||
|
# "Kodi will now restart to apply the changes"
|
||||||
|
dialog.ok(
|
||||||
|
heading=self.addonName,
|
||||||
|
line1=string(33033))
|
||||||
xbmc.executebuiltin('RestartApp')
|
xbmc.executebuiltin('RestartApp')
|
||||||
# We should always restart to ensure e.g. Kodi settings for Music
|
# We should always restart to ensure e.g. Kodi settings for Music
|
||||||
# are in use!
|
# are in use!
|
||||||
|
|
|
@ -11,7 +11,6 @@ import xbmcgui
|
||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
|
|
||||||
import artwork
|
import artwork
|
||||||
import downloadutils
|
|
||||||
import utils
|
import utils
|
||||||
import embydb_functions as embydb
|
import embydb_functions as embydb
|
||||||
import kodidb_functions as kodidb
|
import kodidb_functions as kodidb
|
||||||
|
@ -34,7 +33,6 @@ class Items(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.doUtils = downloadutils.DownloadUtils()
|
|
||||||
self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
||||||
# self.directpath = utils.settings('useDirectPaths') == "1"
|
# self.directpath = utils.settings('useDirectPaths') == "1"
|
||||||
self.directpath = True if utils.window('useDirectPaths') == 'true' \
|
self.directpath = True if utils.window('useDirectPaths') == 'true' \
|
||||||
|
@ -264,14 +262,12 @@ class Items(object):
|
||||||
# If the playback was stopped, check whether we need to increment the
|
# If the playback was stopped, check whether we need to increment the
|
||||||
# playcount. PMS won't tell us the playcount via websockets
|
# playcount. PMS won't tell us the playcount via websockets
|
||||||
if item['state'] in ('stopped', 'ended'):
|
if item['state'] in ('stopped', 'ended'):
|
||||||
|
markPlayed = 0.90
|
||||||
complete = float(item['viewOffset']) / float(item['duration'])
|
complete = float(item['viewOffset']) / float(item['duration'])
|
||||||
complete = complete * 100
|
|
||||||
self.logMsg('Item %s stopped with completion rate %s percent.'
|
self.logMsg('Item %s stopped with completion rate %s percent.'
|
||||||
'Mark item played at %s percent.'
|
'Mark item played at %s percent.'
|
||||||
% (item['ratingKey'],
|
% (item['ratingKey'], str(complete), markPlayed), 1)
|
||||||
str(complete),
|
if complete >= markPlayed:
|
||||||
utils.settings('markPlayed')), 1)
|
|
||||||
if complete >= float(utils.settings('markPlayed')):
|
|
||||||
self.logMsg('Marking as completely watched in Kodi', 1)
|
self.logMsg('Marking as completely watched in Kodi', 1)
|
||||||
try:
|
try:
|
||||||
item['viewCount'] += 1
|
item['viewCount'] += 1
|
||||||
|
@ -309,31 +305,17 @@ class Movies(Items):
|
||||||
count = 0
|
count = 0
|
||||||
for boxset in items:
|
for boxset in items:
|
||||||
|
|
||||||
title = boxset['Name']
|
|
||||||
if pdialog:
|
if pdialog:
|
||||||
percentage = int((float(count) / float(total))*100)
|
percentage = int((float(count) / float(total))*100)
|
||||||
pdialog.update(percentage, message=title)
|
pdialog.update(percentage, message=boxset['Name'])
|
||||||
count += 1
|
count += 1
|
||||||
self.add_updateBoxset(boxset)
|
self.add_updateBoxset(boxset)
|
||||||
|
|
||||||
|
@utils.CatchExceptions(warnuser=True)
|
||||||
def add_update(self, item, viewtag=None, viewid=None):
|
def add_update(self, item, viewtag=None, viewid=None):
|
||||||
try:
|
|
||||||
self.run_add_update(item, viewtag, viewid)
|
|
||||||
except Exception as e:
|
|
||||||
self.logMsg('itemtypes.py for movies has crashed for item %s. '
|
|
||||||
'Error:' % item.attrib.get('ratingKey', None), -1)
|
|
||||||
self.logMsg(e, -1)
|
|
||||||
import traceback
|
|
||||||
self.logMsg("Traceback:\n%s" % traceback.format_exc(), 0)
|
|
||||||
utils.window('plex_scancrashed', value='true')
|
|
||||||
# skip this item for now
|
|
||||||
return
|
|
||||||
|
|
||||||
def run_add_update(self, item, viewtag=None, viewid=None):
|
|
||||||
# Process single movie
|
# Process single movie
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
kodi_db = self.kodi_db
|
|
||||||
artwork = self.artwork
|
artwork = self.artwork
|
||||||
API = PlexAPI.API(item)
|
API = PlexAPI.API(item)
|
||||||
|
|
||||||
|
@ -417,7 +399,7 @@ class Movies(Items):
|
||||||
doIndirect = not self.directpath
|
doIndirect = not self.directpath
|
||||||
if self.directpath:
|
if self.directpath:
|
||||||
# Direct paths is set the Kodi way
|
# Direct paths is set the Kodi way
|
||||||
playurl = API.getFilePath()
|
playurl = API.getFilePath(forceFirstMediaStream=True)
|
||||||
if playurl is None:
|
if playurl is None:
|
||||||
# Something went wrong, trying to use non-direct paths
|
# Something went wrong, trying to use non-direct paths
|
||||||
doIndirect = True
|
doIndirect = True
|
||||||
|
@ -436,7 +418,7 @@ class Movies(Items):
|
||||||
# Set plugin path and media flags using real filename
|
# Set plugin path and media flags using real filename
|
||||||
path = "plugin://plugin.video.plexkodiconnect.movies/"
|
path = "plugin://plugin.video.plexkodiconnect.movies/"
|
||||||
params = {
|
params = {
|
||||||
'filename': API.getKey().encode('utf-8'),
|
'filename': API.getKey(),
|
||||||
'id': itemid,
|
'id': itemid,
|
||||||
'dbid': movieid,
|
'dbid': movieid,
|
||||||
'mode': "play"
|
'mode': "play"
|
||||||
|
@ -444,11 +426,6 @@ class Movies(Items):
|
||||||
filename = "%s?%s" % (path, urllib.urlencode(params))
|
filename = "%s?%s" % (path, urllib.urlencode(params))
|
||||||
playurl = filename
|
playurl = filename
|
||||||
|
|
||||||
# Even if the item is only updated, the file may have been moved or updated.
|
|
||||||
# In the worst case we get exactly the same values as we had before.
|
|
||||||
pathid = kodi_db.addPath(path)
|
|
||||||
fileid = kodi_db.addFile(filename, pathid)
|
|
||||||
|
|
||||||
# movie table:
|
# movie table:
|
||||||
# c22 - playurl
|
# c22 - playurl
|
||||||
# c23 - pathid
|
# c23 - pathid
|
||||||
|
@ -477,6 +454,10 @@ class Movies(Items):
|
||||||
##### OR ADD THE MOVIE #####
|
##### OR ADD THE MOVIE #####
|
||||||
else:
|
else:
|
||||||
self.logMsg("ADD movie itemid: %s - Title: %s" % (itemid, title), 1)
|
self.logMsg("ADD movie itemid: %s - Title: %s" % (itemid, title), 1)
|
||||||
|
# Add path
|
||||||
|
pathid = self.kodi_db.addPath(path)
|
||||||
|
# Add the file
|
||||||
|
fileid = self.kodi_db.addFile(filename, pathid)
|
||||||
|
|
||||||
# Create the movie entry
|
# Create the movie entry
|
||||||
query = (
|
query = (
|
||||||
|
@ -514,28 +495,30 @@ class Movies(Items):
|
||||||
kodicursor.execute(query, (pathid, filename, dateadded, fileid))
|
kodicursor.execute(query, (pathid, filename, dateadded, fileid))
|
||||||
|
|
||||||
# Process countries
|
# Process countries
|
||||||
kodi_db.addCountries(movieid, countries, "movie")
|
self.kodi_db.addCountries(movieid, countries, "movie")
|
||||||
# Process cast
|
# Process cast
|
||||||
people = API.getPeopleList()
|
people = API.getPeopleList()
|
||||||
kodi_db.addPeople(movieid, people, "movie")
|
self.kodi_db.addPeople(movieid, people, "movie")
|
||||||
# Process genres
|
# Process genres
|
||||||
kodi_db.addGenres(movieid, genres, "movie")
|
self.kodi_db.addGenres(movieid, genres, "movie")
|
||||||
# Process artwork
|
# Process artwork
|
||||||
allartworks = API.getAllArtwork()
|
allartworks = API.getAllArtwork()
|
||||||
artwork.addArtwork(allartworks, movieid, "movie", kodicursor)
|
artwork.addArtwork(allartworks, movieid, "movie", kodicursor)
|
||||||
# Process stream details
|
# Process stream details
|
||||||
streams = API.getMediaStreams()
|
streams = API.getMediaStreams()
|
||||||
kodi_db.addStreams(fileid, streams, runtime)
|
self.kodi_db.addStreams(fileid, streams, runtime)
|
||||||
# Process studios
|
# Process studios
|
||||||
kodi_db.addStudios(movieid, studios, "movie")
|
self.kodi_db.addStudios(movieid, studios, "movie")
|
||||||
# Process tags: view, Plex collection tags
|
# Process tags: view, Plex collection tags
|
||||||
tags = [viewtag]
|
tags = [viewtag]
|
||||||
tags.extend(collections)
|
tags.extend(collections)
|
||||||
if userdata['Favorite']:
|
if userdata['Favorite']:
|
||||||
tags.append("Favorite movies")
|
tags.append("Favorite movies")
|
||||||
kodi_db.addTags(movieid, tags, "movie")
|
self.kodi_db.addTags(movieid, tags, "movie")
|
||||||
|
# Add any sets from Plex collection tags
|
||||||
|
self.kodi_db.addSets(movieid, collections, kodicursor, API)
|
||||||
# Process playstates
|
# Process playstates
|
||||||
kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
||||||
|
|
||||||
def remove(self, itemid):
|
def remove(self, itemid):
|
||||||
# Remove movieid, fileid, emby reference
|
# Remove movieid, fileid, emby reference
|
||||||
|
@ -566,11 +549,11 @@ class Movies(Items):
|
||||||
# Delete kodi boxset
|
# Delete kodi boxset
|
||||||
boxset_movies = emby_db.getItem_byParentId(kodiid, "movie")
|
boxset_movies = emby_db.getItem_byParentId(kodiid, "movie")
|
||||||
for movie in boxset_movies:
|
for movie in boxset_movies:
|
||||||
embyid = movie[0]
|
plexid = movie[0]
|
||||||
movieid = movie[1]
|
movieid = movie[1]
|
||||||
self.kodi_db.removefromBoxset(movieid)
|
self.kodi_db.removefromBoxset(movieid)
|
||||||
# Update emby reference
|
# Update emby reference
|
||||||
emby_db.updateParentId(embyid, None)
|
emby_db.updateParentId(plexid, None)
|
||||||
|
|
||||||
kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodiid,))
|
kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodiid,))
|
||||||
|
|
||||||
|
@ -603,9 +586,8 @@ class MusicVideos(Items):
|
||||||
# Process single music video
|
# Process single music video
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
kodi_db = self.kodi_db
|
|
||||||
artwork = self.artwork
|
artwork = self.artwork
|
||||||
API = api.API(item)
|
API = PlexAPI.API(item)
|
||||||
|
|
||||||
# If the item already exist in the local Kodi DB we'll perform a full item update
|
# If the item already exist in the local Kodi DB we'll perform a full item update
|
||||||
# If the item doesn't exist, we'll add it to the database
|
# If the item doesn't exist, we'll add it to the database
|
||||||
|
@ -638,7 +620,7 @@ class MusicVideos(Items):
|
||||||
|
|
||||||
if not viewtag or not viewid:
|
if not viewtag or not viewid:
|
||||||
# Get view tag from emby
|
# Get view tag from emby
|
||||||
viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
|
viewtag, viewid, mediatype = self.emby.getView_plexid(itemid)
|
||||||
self.logMsg("View tag found: %s" % viewtag, 2)
|
self.logMsg("View tag found: %s" % viewtag, 2)
|
||||||
|
|
||||||
# fileId information
|
# fileId information
|
||||||
|
@ -675,7 +657,7 @@ class MusicVideos(Items):
|
||||||
|
|
||||||
if self.directpath:
|
if self.directpath:
|
||||||
# Direct paths is set the Kodi way
|
# Direct paths is set the Kodi way
|
||||||
if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl):
|
if utils.window('plex_pathverified') != "true" and not xbmcvfs.exists(playurl):
|
||||||
# Validate the path is correct with user intervention
|
# Validate the path is correct with user intervention
|
||||||
resp = xbmcgui.Dialog().yesno(
|
resp = xbmcgui.Dialog().yesno(
|
||||||
heading="Can't validate path",
|
heading="Can't validate path",
|
||||||
|
@ -686,17 +668,17 @@ class MusicVideos(Items):
|
||||||
"to format your path correctly. Stop syncing?"
|
"to format your path correctly. Stop syncing?"
|
||||||
% playurl))
|
% playurl))
|
||||||
if resp:
|
if resp:
|
||||||
utils.window('emby_shouldStop', value="true")
|
utils.window('plex_shouldStop', value="true")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
path = playurl.replace(filename, "")
|
path = playurl.replace(filename, "")
|
||||||
utils.window('emby_pathverified', value="true")
|
utils.window('plex_pathverified', value="true")
|
||||||
else:
|
else:
|
||||||
# Set plugin path and media flags using real filename
|
# Set plugin path and media flags using real filename
|
||||||
path = "plugin://plugin.video.plexkodiconnect.musicvideos/"
|
path = "plugin://plugin.video.plexkodiconnect.musicvideos/"
|
||||||
params = {
|
params = {
|
||||||
|
|
||||||
'filename': filename.encode('utf-8'),
|
'filename': utils.tryEncode(filename),
|
||||||
'id': itemid,
|
'id': itemid,
|
||||||
'dbid': mvideoid,
|
'dbid': mvideoid,
|
||||||
'mode': "play"
|
'mode': "play"
|
||||||
|
@ -794,32 +776,31 @@ class MusicVideos(Items):
|
||||||
artist['Type'] = "Artist"
|
artist['Type'] = "Artist"
|
||||||
people.extend(artists)
|
people.extend(artists)
|
||||||
people = artwork.getPeopleArtwork(people)
|
people = artwork.getPeopleArtwork(people)
|
||||||
kodi_db.addPeople(mvideoid, people, "musicvideo")
|
self.kodi_db.addPeople(mvideoid, people, "musicvideo")
|
||||||
# Process genres
|
# Process genres
|
||||||
kodi_db.addGenres(mvideoid, genres, "musicvideo")
|
self.kodi_db.addGenres(mvideoid, genres, "musicvideo")
|
||||||
# Process artwork
|
# Process artwork
|
||||||
artwork.addArtwork(artwork.getAllArtwork(item), mvideoid, "musicvideo", kodicursor)
|
artwork.addArtwork(artwork.getAllArtwork(item), mvideoid, "musicvideo", kodicursor)
|
||||||
# Process stream details
|
# Process stream details
|
||||||
streams = API.getMediaStreams()
|
streams = API.getMediaStreams()
|
||||||
kodi_db.addStreams(fileid, streams, runtime)
|
self.kodi_db.addStreams(fileid, streams, runtime)
|
||||||
# Process studios
|
# Process studios
|
||||||
kodi_db.addStudios(mvideoid, studios, "musicvideo")
|
self.kodi_db.addStudios(mvideoid, studios, "musicvideo")
|
||||||
# Process tags: view, emby tags
|
# Process tags: view, emby tags
|
||||||
tags = [viewtag]
|
tags = [viewtag]
|
||||||
tags.extend(item['Tags'])
|
tags.extend(item['Tags'])
|
||||||
if userdata['Favorite']:
|
if userdata['Favorite']:
|
||||||
tags.append("Favorite musicvideos")
|
tags.append("Favorite musicvideos")
|
||||||
kodi_db.addTags(mvideoid, tags, "musicvideo")
|
self.kodi_db.addTags(mvideoid, tags, "musicvideo")
|
||||||
# Process playstates
|
# Process playstates
|
||||||
resume = API.adjustResume(userdata['Resume'])
|
resume = API.adjustResume(userdata['Resume'])
|
||||||
total = round(float(runtime), 6)
|
total = round(float(runtime), 6)
|
||||||
kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
|
self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
|
||||||
|
|
||||||
def updateUserdata(self, item):
|
def updateUserdata(self, item):
|
||||||
# This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
|
# This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
|
||||||
# Poster with progress bar
|
# Poster with progress bar
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
kodi_db = self.kodi_db
|
|
||||||
API = api.API(item)
|
API = api.API(item)
|
||||||
|
|
||||||
# Get emby information
|
# Get emby information
|
||||||
|
@ -841,9 +822,9 @@ class MusicVideos(Items):
|
||||||
|
|
||||||
# Process favorite tags
|
# Process favorite tags
|
||||||
if userdata['Favorite']:
|
if userdata['Favorite']:
|
||||||
kodi_db.addTag(mvideoid, "Favorite musicvideos", "musicvideo")
|
self.kodi_db.addTag(mvideoid, "Favorite musicvideos", "musicvideo")
|
||||||
else:
|
else:
|
||||||
kodi_db.removeTag(mvideoid, "Favorite musicvideos", "musicvideo")
|
self.kodi_db.removeTag(mvideoid, "Favorite musicvideos", "musicvideo")
|
||||||
|
|
||||||
# Process playstates
|
# Process playstates
|
||||||
playcount = userdata['PlayCount']
|
playcount = userdata['PlayCount']
|
||||||
|
@ -851,7 +832,7 @@ class MusicVideos(Items):
|
||||||
resume = API.adjustResume(userdata['Resume'])
|
resume = API.adjustResume(userdata['Resume'])
|
||||||
total = round(float(runtime), 6)
|
total = round(float(runtime), 6)
|
||||||
|
|
||||||
kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
|
self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
|
||||||
emby_db.updateReference(itemid, checksum)
|
emby_db.updateReference(itemid, checksum)
|
||||||
|
|
||||||
def remove(self, itemid):
|
def remove(self, itemid):
|
||||||
|
@ -878,8 +859,7 @@ class MusicVideos(Items):
|
||||||
"AND media_type = 'musicvideo'"
|
"AND media_type = 'musicvideo'"
|
||||||
))
|
))
|
||||||
kodicursor.execute(query, (mvideoid,))
|
kodicursor.execute(query, (mvideoid,))
|
||||||
rows = kodicursor.fetchall()
|
for row in kodicursor.fetchall():
|
||||||
for row in rows:
|
|
||||||
|
|
||||||
url = row[0]
|
url = row[0]
|
||||||
imagetype = row[1]
|
imagetype = row[1]
|
||||||
|
@ -942,24 +922,11 @@ class TVShows(Items):
|
||||||
if not pdialog and self.contentmsg:
|
if not pdialog and self.contentmsg:
|
||||||
self.contentPop(title, self.newvideo_time)
|
self.contentPop(title, self.newvideo_time)
|
||||||
|
|
||||||
|
@utils.CatchExceptions(warnuser=True)
|
||||||
def add_update(self, item, viewtag=None, viewid=None):
|
def add_update(self, item, viewtag=None, viewid=None):
|
||||||
try:
|
|
||||||
self.run_add_update(item, viewtag, viewid)
|
|
||||||
except Exception as e:
|
|
||||||
self.logMsg('itemtypes.py for tv show has crashed for item %s. '
|
|
||||||
'Error:' % item.attrib.get('ratingKey', None), -1)
|
|
||||||
self.logMsg(e, -1)
|
|
||||||
import traceback
|
|
||||||
self.logMsg("Traceback:\n%s" % traceback.format_exc(), 0)
|
|
||||||
utils.window('plex_scancrashed', value='true')
|
|
||||||
# skip this item for now
|
|
||||||
return
|
|
||||||
|
|
||||||
def run_add_update(self, item, viewtag=None, viewid=None):
|
|
||||||
# Process single tvshow
|
# Process single tvshow
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
kodi_db = self.kodi_db
|
|
||||||
artwork = self.artwork
|
artwork = self.artwork
|
||||||
API = PlexAPI.API(item)
|
API = PlexAPI.API(item)
|
||||||
|
|
||||||
|
@ -997,7 +964,7 @@ class TVShows(Items):
|
||||||
|
|
||||||
if viewtag is None or viewid is None:
|
if viewtag is None or viewid is None:
|
||||||
# Get view tag from emby
|
# Get view tag from emby
|
||||||
viewtag, viewid, mediatype = embyserver.getView_embyId(itemid)
|
viewtag, viewid, mediatype = embyserver.getView_plexid(itemid)
|
||||||
self.logMsg("View tag found: %s" % viewtag, 2)
|
self.logMsg("View tag found: %s" % viewtag, 2)
|
||||||
|
|
||||||
# fileId information
|
# fileId information
|
||||||
|
@ -1047,7 +1014,7 @@ class TVShows(Items):
|
||||||
path = "%s%s/" % (toplevelpath, itemid)
|
path = "%s%s/" % (toplevelpath, itemid)
|
||||||
|
|
||||||
# Add top path
|
# Add top path
|
||||||
toppathid = kodi_db.addPath(toplevelpath)
|
toppathid = self.kodi_db.addPath(toplevelpath)
|
||||||
# UPDATE THE TVSHOW #####
|
# UPDATE THE TVSHOW #####
|
||||||
if update_item:
|
if update_item:
|
||||||
self.logMsg("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1)
|
self.logMsg("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1)
|
||||||
|
@ -1079,7 +1046,7 @@ class TVShows(Items):
|
||||||
kodicursor.execute(query, (toplevelpath, "tvshows", "metadata.local", 1, toppathid))
|
kodicursor.execute(query, (toplevelpath, "tvshows", "metadata.local", 1, toppathid))
|
||||||
|
|
||||||
# Add path
|
# Add path
|
||||||
pathid = kodi_db.addPath(path)
|
pathid = self.kodi_db.addPath(path)
|
||||||
|
|
||||||
# Create the tvshow entry
|
# Create the tvshow entry
|
||||||
query = (
|
query = (
|
||||||
|
@ -1112,18 +1079,18 @@ class TVShows(Items):
|
||||||
|
|
||||||
# Process cast
|
# Process cast
|
||||||
people = API.getPeopleList()
|
people = API.getPeopleList()
|
||||||
kodi_db.addPeople(showid, people, "tvshow")
|
self.kodi_db.addPeople(showid, people, "tvshow")
|
||||||
# Process genres
|
# Process genres
|
||||||
kodi_db.addGenres(showid, genres, "tvshow")
|
self.kodi_db.addGenres(showid, genres, "tvshow")
|
||||||
# Process artwork
|
# Process artwork
|
||||||
allartworks = API.getAllArtwork()
|
allartworks = API.getAllArtwork()
|
||||||
artwork.addArtwork(allartworks, showid, "tvshow", kodicursor)
|
artwork.addArtwork(allartworks, showid, "tvshow", kodicursor)
|
||||||
# Process studios
|
# Process studios
|
||||||
kodi_db.addStudios(showid, studios, "tvshow")
|
self.kodi_db.addStudios(showid, studios, "tvshow")
|
||||||
# Process tags: view, PMS collection tags
|
# Process tags: view, PMS collection tags
|
||||||
tags = [viewtag]
|
tags = [viewtag]
|
||||||
tags.extend(collections)
|
tags.extend(collections)
|
||||||
kodi_db.addTags(showid, tags, "tvshow")
|
self.kodi_db.addTags(showid, tags, "tvshow")
|
||||||
|
|
||||||
if force_episodes:
|
if force_episodes:
|
||||||
# We needed to recreate the show entry. Re-add episodes now.
|
# We needed to recreate the show entry. Re-add episodes now.
|
||||||
|
@ -1133,20 +1100,8 @@ class TVShows(Items):
|
||||||
self.kodiconn.commit()
|
self.kodiconn.commit()
|
||||||
self.embyconn.commit()
|
self.embyconn.commit()
|
||||||
|
|
||||||
|
@utils.CatchExceptions(warnuser=True)
|
||||||
def add_updateSeason(self, item, viewtag=None, viewid=None):
|
def add_updateSeason(self, item, viewtag=None, viewid=None):
|
||||||
try:
|
|
||||||
self.run_add_updateSeason(item, viewtag, viewid)
|
|
||||||
except Exception as e:
|
|
||||||
self.logMsg('itemtypes.py for tv seasons has crashed for item %s. '
|
|
||||||
'Error:' % item.attrib.get('ratingKey', None), -1)
|
|
||||||
self.logMsg(e, -1)
|
|
||||||
import traceback
|
|
||||||
self.logMsg("Traceback:\n%s" % traceback.format_exc(), 0)
|
|
||||||
utils.window('plex_scancrashed', value='true')
|
|
||||||
# skip this item for now
|
|
||||||
return
|
|
||||||
|
|
||||||
def run_add_updateSeason(self, item, viewtag=None, viewid=None):
|
|
||||||
API = PlexAPI.API(item)
|
API = PlexAPI.API(item)
|
||||||
itemid = API.getRatingKey()
|
itemid = API.getRatingKey()
|
||||||
if not itemid:
|
if not itemid:
|
||||||
|
@ -1154,7 +1109,6 @@ class TVShows(Items):
|
||||||
return
|
return
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
kodi_db = self.kodi_db
|
|
||||||
artwork = self.artwork
|
artwork = self.artwork
|
||||||
seasonnum = API.getIndex()
|
seasonnum = API.getIndex()
|
||||||
# Get parent tv show Plex id
|
# Get parent tv show Plex id
|
||||||
|
@ -1169,7 +1123,7 @@ class TVShows(Items):
|
||||||
% (itemid), -1)
|
% (itemid), -1)
|
||||||
return
|
return
|
||||||
|
|
||||||
seasonid = kodi_db.addSeason(showid, seasonnum)
|
seasonid = self.kodi_db.addSeason(showid, seasonnum)
|
||||||
checksum = API.getChecksum()
|
checksum = API.getChecksum()
|
||||||
# Check whether Season already exists
|
# Check whether Season already exists
|
||||||
update_item = True
|
update_item = True
|
||||||
|
@ -1192,28 +1146,14 @@ class TVShows(Items):
|
||||||
self.kodiconn.commit()
|
self.kodiconn.commit()
|
||||||
self.embyconn.commit()
|
self.embyconn.commit()
|
||||||
|
|
||||||
|
@utils.CatchExceptions(warnuser=True)
|
||||||
def add_updateEpisode(self, item, viewtag=None, viewid=None):
|
def add_updateEpisode(self, item, viewtag=None, viewid=None):
|
||||||
try:
|
|
||||||
self.run_add_updateEpisode(item, viewtag, viewid)
|
|
||||||
except Exception as e:
|
|
||||||
self.logMsg('itemtypes.py for tv episode has crashed for item %s. '
|
|
||||||
'Error:' % item.attrib.get('ratingKey', None), -1)
|
|
||||||
self.logMsg(e, -1)
|
|
||||||
import traceback
|
|
||||||
self.logMsg("Traceback:\n%s" % traceback.format_exc(), 0)
|
|
||||||
utils.window('plex_scancrashed', value='true')
|
|
||||||
# skip this item for now
|
|
||||||
return
|
|
||||||
|
|
||||||
def run_add_updateEpisode(self, item, viewtag=None, viewid=None):
|
|
||||||
"""
|
"""
|
||||||
viewtag and viewid are irrelevant!
|
viewtag and viewid are irrelevant!
|
||||||
"""
|
"""
|
||||||
# Process single episode
|
# Process single episode
|
||||||
kodiversion = self.kodiversion
|
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
kodi_db = self.kodi_db
|
|
||||||
artwork = self.artwork
|
artwork = self.artwork
|
||||||
API = PlexAPI.API(item)
|
API = PlexAPI.API(item)
|
||||||
|
|
||||||
|
@ -1310,11 +1250,11 @@ class TVShows(Items):
|
||||||
# self.logMsg("Skipping: %s. Unable to add series: %s." % (itemid, seriesId), -1)
|
# self.logMsg("Skipping: %s. Unable to add series: %s." % (itemid, seriesId), -1)
|
||||||
self.logMsg("Parent tvshow now found, skip item", 2)
|
self.logMsg("Parent tvshow now found, skip item", 2)
|
||||||
return False
|
return False
|
||||||
seasonid = kodi_db.addSeason(showid, season)
|
seasonid = self.kodi_db.addSeason(showid, season)
|
||||||
|
|
||||||
# GET THE FILE AND PATH #####
|
# GET THE FILE AND PATH #####
|
||||||
doIndirect = not self.directpath
|
doIndirect = not self.directpath
|
||||||
playurl = API.getFilePath()
|
playurl = API.getFilePath(forceFirstMediaStream=True)
|
||||||
if self.directpath:
|
if self.directpath:
|
||||||
# Direct paths is set the Kodi way
|
# Direct paths is set the Kodi way
|
||||||
if playurl is None:
|
if playurl is None:
|
||||||
|
@ -1331,7 +1271,7 @@ class TVShows(Items):
|
||||||
# Network share
|
# Network share
|
||||||
filename = playurl.rsplit("/", 1)[1]
|
filename = playurl.rsplit("/", 1)[1]
|
||||||
path = playurl.replace(filename, "")
|
path = playurl.replace(filename, "")
|
||||||
parentPathId = kodi_db.getParentPathId(path)
|
parentPathId = self.kodi_db.getParentPathId(path)
|
||||||
if doIndirect:
|
if doIndirect:
|
||||||
# Set plugin path and media flags using real filename
|
# Set plugin path and media flags using real filename
|
||||||
if playurl is not None:
|
if playurl is not None:
|
||||||
|
@ -1340,24 +1280,20 @@ class TVShows(Items):
|
||||||
else:
|
else:
|
||||||
filename = playurl.rsplit('/', 1)[1]
|
filename = playurl.rsplit('/', 1)[1]
|
||||||
else:
|
else:
|
||||||
filename = 'file_not_found'
|
filename = 'file_not_found.mkv'
|
||||||
path = "plugin://plugin.video.plexkodiconnect.tvshows/%s/" % seriesId
|
path = "plugin://plugin.video.plexkodiconnect.tvshows/%s/" % seriesId
|
||||||
params = {
|
params = {
|
||||||
'filename': filename.encode('utf-8'),
|
'filename': utils.tryEncode(filename),
|
||||||
'id': itemid,
|
'id': itemid,
|
||||||
'dbid': episodeid,
|
'dbid': episodeid,
|
||||||
'mode': "play"
|
'mode': "play"
|
||||||
}
|
}
|
||||||
filename = "%s?%s" % (path, urllib.urlencode(params))
|
filename = "%s?%s" % (path,
|
||||||
|
utils.tryDecode(urllib.urlencode(params)))
|
||||||
playurl = filename
|
playurl = filename
|
||||||
parentPathId = kodi_db.addPath(
|
parentPathId = self.kodi_db.addPath(
|
||||||
'plugin://plugin.video.plexkodiconnect.tvshows/')
|
'plugin://plugin.video.plexkodiconnect.tvshows/')
|
||||||
|
|
||||||
# Even if the item is only updated, the file may have been moved or updated.
|
|
||||||
# In the worst case we get exactly the same values as we had before.
|
|
||||||
pathid = kodi_db.addPath(path)
|
|
||||||
fileid = kodi_db.addFile(filename, pathid)
|
|
||||||
|
|
||||||
# episodes table:
|
# episodes table:
|
||||||
# c18 - playurl
|
# c18 - playurl
|
||||||
# c19 - pathid
|
# c19 - pathid
|
||||||
|
@ -1368,7 +1304,7 @@ class TVShows(Items):
|
||||||
self.logMsg("UPDATE episode itemid: %s" % (itemid), 1)
|
self.logMsg("UPDATE episode itemid: %s" % (itemid), 1)
|
||||||
|
|
||||||
# Update the movie entry
|
# Update the movie entry
|
||||||
if kodiversion in (16, 17):
|
if self.kodiversion in (16, 17):
|
||||||
# Kodi Jarvis, Krypton
|
# Kodi Jarvis, Krypton
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
|
@ -1400,10 +1336,13 @@ class TVShows(Items):
|
||||||
|
|
||||||
##### OR ADD THE EPISODE #####
|
##### OR ADD THE EPISODE #####
|
||||||
else:
|
else:
|
||||||
self.logMsg("ADD episode itemid: %s" % (itemid), 1)
|
self.logMsg("ADD episode itemid: %s - Title: %s" % (itemid, title), 1)
|
||||||
|
# Add path
|
||||||
|
pathid = self.kodi_db.addPath(path)
|
||||||
|
# Add the file
|
||||||
|
fileid = self.kodi_db.addFile(filename, pathid)
|
||||||
# Create the episode entry
|
# Create the episode entry
|
||||||
if kodiversion in (16, 17):
|
if self.kodiversion in (16, 17):
|
||||||
# Kodi Jarvis, Krypton
|
# Kodi Jarvis, Krypton
|
||||||
query = (
|
query = (
|
||||||
'''
|
'''
|
||||||
|
@ -1454,7 +1393,7 @@ class TVShows(Items):
|
||||||
kodicursor.execute(query, (pathid, filename, dateadded, fileid))
|
kodicursor.execute(query, (pathid, filename, dateadded, fileid))
|
||||||
# Process cast
|
# Process cast
|
||||||
people = API.getPeopleList()
|
people = API.getPeopleList()
|
||||||
kodi_db.addPeople(episodeid, people, "episode")
|
self.kodi_db.addPeople(episodeid, people, "episode")
|
||||||
# Process artwork
|
# Process artwork
|
||||||
# Wide "screenshot" of particular episode
|
# Wide "screenshot" of particular episode
|
||||||
poster = item.attrib.get('thumb')
|
poster = item.attrib.get('thumb')
|
||||||
|
@ -1473,13 +1412,13 @@ class TVShows(Items):
|
||||||
|
|
||||||
# Process stream details
|
# Process stream details
|
||||||
streams = API.getMediaStreams()
|
streams = API.getMediaStreams()
|
||||||
kodi_db.addStreams(fileid, streams, runtime)
|
self.kodi_db.addStreams(fileid, streams, runtime)
|
||||||
# Process playstates
|
# Process playstates
|
||||||
kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
||||||
if not self.directpath and resume:
|
if not self.directpath and resume:
|
||||||
# Create additional entry for widgets. This is only required for plugin/episode.
|
# Create additional entry for widgets. This is only required for plugin/episode.
|
||||||
temppathid = kodi_db.getPath("plugin://plugin.video.plexkodiconnect.tvshows/")
|
temppathid = self.kodi_db.getPath("plugin://plugin.video.plexkodiconnect.tvshows/")
|
||||||
tempfileid = kodi_db.addFile(filename, temppathid)
|
tempfileid = self.kodi_db.addFile(filename, temppathid)
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
"UPDATE files",
|
"UPDATE files",
|
||||||
|
@ -1487,7 +1426,7 @@ class TVShows(Items):
|
||||||
"WHERE idFile = ?"
|
"WHERE idFile = ?"
|
||||||
))
|
))
|
||||||
kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid))
|
kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid))
|
||||||
kodi_db.addPlaystate(tempfileid, resume, runtime, playcount, dateplayed)
|
self.kodi_db.addPlaystate(tempfileid, resume, runtime, playcount, dateplayed)
|
||||||
self.kodiconn.commit()
|
self.kodiconn.commit()
|
||||||
self.embyconn.commit()
|
self.embyconn.commit()
|
||||||
|
|
||||||
|
@ -1600,27 +1539,23 @@ class TVShows(Items):
|
||||||
def removeShow(self, kodiid):
|
def removeShow(self, kodiid):
|
||||||
|
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
artwork = self.artwork
|
self.artwork.deleteArtwork(kodiid, "tvshow", kodicursor)
|
||||||
|
|
||||||
artwork.deleteArtwork(kodiid, "tvshow", kodicursor)
|
|
||||||
kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodiid,))
|
kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodiid,))
|
||||||
self.logMsg("Removed tvshow: %s." % kodiid, 2)
|
self.logMsg("Removed tvshow: %s." % kodiid, 2)
|
||||||
|
|
||||||
def removeSeason(self, kodiid):
|
def removeSeason(self, kodiid):
|
||||||
|
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
artwork = self.artwork
|
|
||||||
|
|
||||||
artwork.deleteArtwork(kodiid, "season", kodicursor)
|
self.artwork.deleteArtwork(kodiid, "season", kodicursor)
|
||||||
kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodiid,))
|
kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodiid,))
|
||||||
self.logMsg("Removed season: %s." % kodiid, 2)
|
self.logMsg("Removed season: %s." % kodiid, 2)
|
||||||
|
|
||||||
def removeEpisode(self, kodiid, fileid):
|
def removeEpisode(self, kodiid, fileid):
|
||||||
|
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
artwork = self.artwork
|
|
||||||
|
|
||||||
artwork.deleteArtwork(kodiid, "episode", kodicursor)
|
self.artwork.deleteArtwork(kodiid, "episode", kodicursor)
|
||||||
kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodiid,))
|
kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodiid,))
|
||||||
kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,))
|
kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,))
|
||||||
self.logMsg("Removed episode: %s." % kodiid, 2)
|
self.logMsg("Removed episode: %s." % kodiid, 2)
|
||||||
|
@ -1656,10 +1591,9 @@ class Music(Items):
|
||||||
count = 0
|
count = 0
|
||||||
for artist in items:
|
for artist in items:
|
||||||
|
|
||||||
title = artist['Name']
|
|
||||||
if pdialog:
|
if pdialog:
|
||||||
percentage = int((float(count) / float(total))*100)
|
percentage = int((float(count) / float(total))*100)
|
||||||
pdialog.update(percentage, message=title)
|
pdialog.update(percentage, message=artist['Name'])
|
||||||
count += 1
|
count += 1
|
||||||
self.add_updateArtist(artist)
|
self.add_updateArtist(artist)
|
||||||
# Add albums
|
# Add albums
|
||||||
|
@ -1672,10 +1606,9 @@ class Music(Items):
|
||||||
count = 0
|
count = 0
|
||||||
for album in items:
|
for album in items:
|
||||||
|
|
||||||
title = album['Name']
|
|
||||||
if pdialog:
|
if pdialog:
|
||||||
percentage = int((float(count) / float(total))*100)
|
percentage = int((float(count) / float(total))*100)
|
||||||
pdialog.update(percentage, message=title)
|
pdialog.update(percentage, message=album['Name'])
|
||||||
count += 1
|
count += 1
|
||||||
self.add_updateAlbum(album)
|
self.add_updateAlbum(album)
|
||||||
# Add songs
|
# Add songs
|
||||||
|
@ -1688,34 +1621,19 @@ class Music(Items):
|
||||||
count = 0
|
count = 0
|
||||||
for song in items:
|
for song in items:
|
||||||
|
|
||||||
title = song['Name']
|
|
||||||
if pdialog:
|
if pdialog:
|
||||||
percentage = int((float(count) / float(total))*100)
|
percentage = int((float(count) / float(total))*100)
|
||||||
pdialog.update(percentage, message=title)
|
pdialog.update(percentage, message=song['Name'])
|
||||||
count += 1
|
count += 1
|
||||||
self.add_updateSong(song)
|
self.add_updateSong(song)
|
||||||
if not pdialog and self.contentmsg:
|
if not pdialog and self.contentmsg:
|
||||||
self.contentPop(title, self.newmusic_time)
|
self.contentPop(song['Name'], self.newmusic_time)
|
||||||
|
|
||||||
def add_updateArtist(self, item, viewtag=None, viewid=None, artisttype="MusicArtist"):
|
@utils.CatchExceptions(warnuser=True)
|
||||||
try:
|
def add_updateArtist(self, item, viewtag=None, viewid=None,
|
||||||
self.run_add_updateArtist(item, viewtag, viewid, artisttype)
|
|
||||||
except Exception as e:
|
|
||||||
self.logMsg('itemtypes.py for music artist has crashed for '
|
|
||||||
'item %s. Error:'
|
|
||||||
% item.attrib.get('ratingKey', None), -1)
|
|
||||||
self.logMsg(e, -1)
|
|
||||||
import traceback
|
|
||||||
self.logMsg("Traceback:\n%s" % traceback.format_exc(), 0)
|
|
||||||
utils.window('plex_scancrashed', value='true')
|
|
||||||
# skip this item for now
|
|
||||||
return
|
|
||||||
|
|
||||||
def run_add_updateArtist(self, item, viewtag=None, viewid=None,
|
|
||||||
artisttype="MusicArtist"):
|
artisttype="MusicArtist"):
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
kodi_db = self.kodi_db
|
|
||||||
artwork = self.artwork
|
artwork = self.artwork
|
||||||
API = PlexAPI.API(item)
|
API = PlexAPI.API(item)
|
||||||
|
|
||||||
|
@ -1764,7 +1682,7 @@ class Music(Items):
|
||||||
# multiple times.
|
# multiple times.
|
||||||
# Kodi doesn't allow that. In case that happens we just merge the
|
# Kodi doesn't allow that. In case that happens we just merge the
|
||||||
# artist entries.
|
# artist entries.
|
||||||
artistid = kodi_db.addArtist(name, musicBrainzId)
|
artistid = self.kodi_db.addArtist(name, musicBrainzId)
|
||||||
# Create the reference in emby table
|
# Create the reference in emby table
|
||||||
emby_db.addReference(
|
emby_db.addReference(
|
||||||
itemid, artistid, artisttype, "artist", checksum=checksum)
|
itemid, artistid, artisttype, "artist", checksum=checksum)
|
||||||
|
@ -1796,25 +1714,10 @@ class Music(Items):
|
||||||
self.embyconn.commit()
|
self.embyconn.commit()
|
||||||
self.kodiconn.commit()
|
self.kodiconn.commit()
|
||||||
|
|
||||||
|
@utils.CatchExceptions(warnuser=True)
|
||||||
def add_updateAlbum(self, item, viewtag=None, viewid=None):
|
def add_updateAlbum(self, item, viewtag=None, viewid=None):
|
||||||
try:
|
|
||||||
self.run_add_updateAlbum(item, viewtag, viewid)
|
|
||||||
except Exception as e:
|
|
||||||
self.logMsg('itemtypes.py for music album has crashed for '
|
|
||||||
'item %s. Error:'
|
|
||||||
% item.attrib.get('ratingKey', None), -1)
|
|
||||||
self.logMsg(e, -1)
|
|
||||||
import traceback
|
|
||||||
self.logMsg("Traceback:\n%s" % traceback.format_exc(), 0)
|
|
||||||
utils.window('plex_scancrashed', value='true')
|
|
||||||
# skip this item for now
|
|
||||||
return
|
|
||||||
|
|
||||||
def run_add_updateAlbum(self, item, viewtag=None, viewid=None):
|
|
||||||
kodiversion = self.kodiversion
|
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
kodi_db = self.kodi_db
|
|
||||||
artwork = self.artwork
|
artwork = self.artwork
|
||||||
API = PlexAPI.API(item)
|
API = PlexAPI.API(item)
|
||||||
|
|
||||||
|
@ -1875,13 +1778,13 @@ class Music(Items):
|
||||||
# multiple times.
|
# multiple times.
|
||||||
# Kodi doesn't allow that. In case that happens we just merge the
|
# Kodi doesn't allow that. In case that happens we just merge the
|
||||||
# artist entries.
|
# artist entries.
|
||||||
albumid = kodi_db.addAlbum(name, musicBrainzId)
|
albumid = self.kodi_db.addAlbum(name, musicBrainzId)
|
||||||
# Create the reference in emby table
|
# Create the reference in emby table
|
||||||
emby_db.addReference(
|
emby_db.addReference(
|
||||||
itemid, albumid, "MusicAlbum", "album", checksum=checksum)
|
itemid, albumid, "MusicAlbum", "album", checksum=checksum)
|
||||||
|
|
||||||
# Process the album info
|
# Process the album info
|
||||||
if kodiversion == 17:
|
if self.kodiversion == 17:
|
||||||
# Kodi Krypton
|
# Kodi Krypton
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
|
@ -1894,7 +1797,7 @@ class Music(Items):
|
||||||
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
|
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
|
||||||
rating, lastScraped, "album", studio,
|
rating, lastScraped, "album", studio,
|
||||||
albumid))
|
albumid))
|
||||||
elif kodiversion == 16:
|
elif self.kodiversion == 16:
|
||||||
# Kodi Jarvis
|
# Kodi Jarvis
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
|
@ -1907,7 +1810,7 @@ class Music(Items):
|
||||||
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
|
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
|
||||||
rating, lastScraped, "album", studio,
|
rating, lastScraped, "album", studio,
|
||||||
albumid))
|
albumid))
|
||||||
elif kodiversion == 15:
|
elif self.kodiversion == 15:
|
||||||
# Kodi Isengard
|
# Kodi Isengard
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
|
@ -1998,33 +1901,18 @@ class Music(Items):
|
||||||
# Update emby reference with parentid
|
# Update emby reference with parentid
|
||||||
emby_db.updateParentId(artistId, albumid)
|
emby_db.updateParentId(artistId, albumid)
|
||||||
# Add genres
|
# Add genres
|
||||||
kodi_db.addMusicGenres(albumid, genres, "album")
|
self.kodi_db.addMusicGenres(albumid, genres, "album")
|
||||||
# Update artwork
|
# Update artwork
|
||||||
artwork.addArtwork(artworks, albumid, "album", kodicursor)
|
artwork.addArtwork(artworks, albumid, "album", kodicursor)
|
||||||
self.embyconn.commit()
|
self.embyconn.commit()
|
||||||
self.kodiconn.commit()
|
self.kodiconn.commit()
|
||||||
|
|
||||||
|
@utils.CatchExceptions(warnuser=True)
|
||||||
def add_updateSong(self, item, viewtag=None, viewid=None):
|
def add_updateSong(self, item, viewtag=None, viewid=None):
|
||||||
try:
|
|
||||||
self.run_add_updateSong(item, viewtag, viewid)
|
|
||||||
except Exception as e:
|
|
||||||
self.logMsg('itemtypes.py for music song has crashed for '
|
|
||||||
'item %s. Error:'
|
|
||||||
% item.attrib.get('ratingKey', None), -1)
|
|
||||||
self.logMsg(e, -1)
|
|
||||||
import traceback
|
|
||||||
self.logMsg("Traceback:\n%s" % traceback.format_exc(), 0)
|
|
||||||
utils.window('plex_scancrashed', value='true')
|
|
||||||
# skip this item for now
|
|
||||||
return
|
|
||||||
|
|
||||||
def run_add_updateSong(self, item, viewtag=None, viewid=None):
|
|
||||||
# Process single song
|
# Process single song
|
||||||
kodiversion = self.kodiversion
|
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
emby = self.emby
|
emby = self.emby
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
kodi_db = self.kodi_db
|
|
||||||
artwork = self.artwork
|
artwork = self.artwork
|
||||||
API = PlexAPI.API(item)
|
API = PlexAPI.API(item)
|
||||||
|
|
||||||
|
@ -2085,7 +1973,7 @@ class Music(Items):
|
||||||
doIndirect = not self.directpath
|
doIndirect = not self.directpath
|
||||||
if self.directpath:
|
if self.directpath:
|
||||||
# Direct paths is set the Kodi way
|
# Direct paths is set the Kodi way
|
||||||
playurl = API.getFilePath()
|
playurl = API.getFilePath(forceFirstMediaStream=True)
|
||||||
if playurl is None:
|
if playurl is None:
|
||||||
# Something went wrong, trying to use non-direct paths
|
# Something went wrong, trying to use non-direct paths
|
||||||
doIndirect = True
|
doIndirect = True
|
||||||
|
@ -2136,7 +2024,7 @@ class Music(Items):
|
||||||
self.logMsg("ADD song itemid: %s - Title: %s" % (itemid, title), 1)
|
self.logMsg("ADD song itemid: %s - Title: %s" % (itemid, title), 1)
|
||||||
|
|
||||||
# Add path
|
# Add path
|
||||||
pathid = kodi_db.addPath(path, strHash="123")
|
pathid = self.kodi_db.addPath(path, strHash="123")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get the album
|
# Get the album
|
||||||
|
@ -2148,7 +2036,7 @@ class Music(Items):
|
||||||
album_name = item.get('parentTitle')
|
album_name = item.get('parentTitle')
|
||||||
if album_name:
|
if album_name:
|
||||||
self.logMsg("Creating virtual music album for song: %s." % itemid, 1)
|
self.logMsg("Creating virtual music album for song: %s." % itemid, 1)
|
||||||
albumid = kodi_db.addAlbum(album_name, API.getProvider('MusicBrainzAlbum'))
|
albumid = self.kodi_db.addAlbum(album_name, API.getProvider('MusicBrainzAlbum'))
|
||||||
emby_db.addReference("%salbum%s" % (itemid, albumid), albumid, "MusicAlbum_", "album")
|
emby_db.addReference("%salbum%s" % (itemid, albumid), albumid, "MusicAlbum_", "album")
|
||||||
else:
|
else:
|
||||||
# No album Id associated to the song.
|
# No album Id associated to the song.
|
||||||
|
@ -2173,7 +2061,7 @@ class Music(Items):
|
||||||
self.logMsg("Failed to add album. Creating singles.", 1)
|
self.logMsg("Failed to add album. Creating singles.", 1)
|
||||||
kodicursor.execute("select coalesce(max(idAlbum),0) from album")
|
kodicursor.execute("select coalesce(max(idAlbum),0) from album")
|
||||||
albumid = kodicursor.fetchone()[0] + 1
|
albumid = kodicursor.fetchone()[0] + 1
|
||||||
if kodiversion == 16:
|
if self.kodiversion == 16:
|
||||||
# Kodi Jarvis
|
# Kodi Jarvis
|
||||||
query = (
|
query = (
|
||||||
'''
|
'''
|
||||||
|
@ -2183,7 +2071,7 @@ class Music(Items):
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
kodicursor.execute(query, (albumid, genre, year, "single"))
|
kodicursor.execute(query, (albumid, genre, year, "single"))
|
||||||
elif kodiversion == 15:
|
elif self.kodiversion == 15:
|
||||||
# Kodi Isengard
|
# Kodi Isengard
|
||||||
query = (
|
query = (
|
||||||
'''
|
'''
|
||||||
|
@ -2262,13 +2150,31 @@ class Music(Items):
|
||||||
artist_edb = emby_db.getItem_byId(artist_eid)
|
artist_edb = emby_db.getItem_byId(artist_eid)
|
||||||
artistid = artist_edb[0]
|
artistid = artist_edb[0]
|
||||||
finally:
|
finally:
|
||||||
query = (
|
if self.kodiversion >= 17:
|
||||||
'''
|
# Kodi Krypton
|
||||||
INSERT OR REPLACE INTO song_artist(idArtist, idSong, iOrder, strArtist)
|
query = (
|
||||||
VALUES (?, ?, ?, ?)
|
'''
|
||||||
'''
|
INSERT OR REPLACE INTO song_artist(idArtist, idSong, idRole, iOrder, strArtist)
|
||||||
)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
kodicursor.execute(query, (artistid, songid, index, artist_name))
|
'''
|
||||||
|
)
|
||||||
|
kodicursor.execute(query,(artistid, songid, 1, index, artist_name))
|
||||||
|
# May want to look into only doing this once?
|
||||||
|
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)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
'''
|
||||||
|
)
|
||||||
|
kodicursor.execute(query, (artistid, songid, index, artist_name))
|
||||||
|
|
||||||
# Verify if album artist exists
|
# Verify if album artist exists
|
||||||
album_artists = []
|
album_artists = []
|
||||||
|
@ -2316,11 +2222,11 @@ class Music(Items):
|
||||||
result = kodicursor.fetchone()
|
result = kodicursor.fetchone()
|
||||||
if result and result[0] != album_artists:
|
if result and result[0] != album_artists:
|
||||||
# Field is empty
|
# Field is empty
|
||||||
if kodiversion in (16, 17):
|
if self.kodiversion in (16, 17):
|
||||||
# Kodi Jarvis, Krypton
|
# Kodi Jarvis, Krypton
|
||||||
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
|
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
|
||||||
kodicursor.execute(query, (album_artists, albumid))
|
kodicursor.execute(query, (album_artists, albumid))
|
||||||
elif kodiversion == 15:
|
elif self.kodiversion == 15:
|
||||||
# Kodi Isengard
|
# Kodi Isengard
|
||||||
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
|
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
|
||||||
kodicursor.execute(query, (album_artists, albumid))
|
kodicursor.execute(query, (album_artists, albumid))
|
||||||
|
@ -2330,7 +2236,7 @@ class Music(Items):
|
||||||
kodicursor.execute(query, (album_artists, albumid))
|
kodicursor.execute(query, (album_artists, albumid))
|
||||||
|
|
||||||
# Add genres
|
# Add genres
|
||||||
kodi_db.addMusicGenres(songid, genres, "song")
|
self.kodi_db.addMusicGenres(songid, genres, "song")
|
||||||
|
|
||||||
# Update artwork
|
# Update artwork
|
||||||
allart = API.getAllArtwork(parentInfo=True)
|
allart = API.getAllArtwork(parentInfo=True)
|
||||||
|
@ -2372,10 +2278,9 @@ class Music(Items):
|
||||||
self.removeSong(kodiid)
|
self.removeSong(kodiid)
|
||||||
# This should only address single song scenario, where server doesn't actually
|
# This should only address single song scenario, where server doesn't actually
|
||||||
# create an album for the song.
|
# create an album for the song.
|
||||||
customitems = emby_db.getItem_byWildId(itemid)
|
|
||||||
emby_db.removeWildItem(itemid)
|
emby_db.removeWildItem(itemid)
|
||||||
|
|
||||||
for item in customitems:
|
for item in emby_db.getItem_byWildId(itemid):
|
||||||
|
|
||||||
item_kid = item[0]
|
item_kid = item[0]
|
||||||
item_mediatype = item[1]
|
item_mediatype = item[1]
|
||||||
|
@ -2431,23 +2336,16 @@ class Music(Items):
|
||||||
def removeSong(self, kodiid):
|
def removeSong(self, kodiid):
|
||||||
|
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
artwork = self.artwork
|
|
||||||
|
|
||||||
artwork.deleteArtwork(kodiid, "song", kodicursor)
|
self.artwork.deleteArtwork(kodiid, "song", self.kodicursor)
|
||||||
kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiid,))
|
self.kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiid,))
|
||||||
|
|
||||||
def removeAlbum(self, kodiid):
|
def removeAlbum(self, kodiid):
|
||||||
|
|
||||||
kodicursor = self.kodicursor
|
self.artwork.deleteArtwork(kodiid, "album", self.kodicursor)
|
||||||
artwork = self.artwork
|
self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiid,))
|
||||||
|
|
||||||
artwork.deleteArtwork(kodiid, "album", kodicursor)
|
|
||||||
kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiid,))
|
|
||||||
|
|
||||||
def removeArtist(self, kodiid):
|
def removeArtist(self, kodiid):
|
||||||
|
|
||||||
kodicursor = self.kodicursor
|
self.artwork.deleteArtwork(kodiid, "artist", self.kodicursor)
|
||||||
artwork = self.artwork
|
self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiid,))
|
||||||
|
|
||||||
artwork.deleteArtwork(kodiid, "artist", kodicursor)
|
|
||||||
kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiid,))
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -24,28 +24,28 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
|
|
||||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||||
self.xbmcplayer = xbmc.Player()
|
self.xbmcplayer = xbmc.Player()
|
||||||
|
xbmc.Monitor.__init__(self)
|
||||||
self.logMsg("Kodi monitor started.", 1)
|
self.logMsg("Kodi monitor started.", 1)
|
||||||
|
|
||||||
def onScanStarted(self, library):
|
def onScanStarted(self, library):
|
||||||
self.logMsg("Kodi library scan %s running." % library, 2)
|
self.logMsg("Kodi library scan %s running." % library, 2)
|
||||||
if library == "video":
|
if library == "video":
|
||||||
utils.window('emby_kodiScan', value="true")
|
utils.window('plex_kodiScan', value="true")
|
||||||
|
|
||||||
def onScanFinished(self, library):
|
def onScanFinished(self, library):
|
||||||
self.logMsg("Kodi library scan %s finished." % library, 2)
|
self.logMsg("Kodi library scan %s finished." % library, 2)
|
||||||
if library == "video":
|
if library == "video":
|
||||||
utils.window('emby_kodiScan', clear=True)
|
utils.window('plex_kodiScan', clear=True)
|
||||||
|
|
||||||
def onSettingsChanged(self):
|
def onSettingsChanged(self):
|
||||||
# Monitor emby settings
|
# Monitor emby settings
|
||||||
# Review reset setting at a later time, need to be adjusted to account for initial setup
|
# Review reset setting at a later time, need to be adjusted to account for initial setup
|
||||||
# changes.
|
# changes.
|
||||||
'''currentPath = utils.settings('useDirectPaths')
|
'''currentPath = utils.settings('useDirectPaths')
|
||||||
if utils.window('emby_pluginpath') != currentPath:
|
if utils.window('plex_pluginpath') != currentPath:
|
||||||
# Plugin path value changed. Offer to reset
|
# Plugin path value changed. Offer to reset
|
||||||
self.logMsg("Changed to playback mode detected", 1)
|
self.logMsg("Changed to playback mode detected", 1)
|
||||||
utils.window('emby_pluginpath', value=currentPath)
|
utils.window('plex_pluginpath', value=currentPath)
|
||||||
resp = xbmcgui.Dialog().yesno(
|
resp = xbmcgui.Dialog().yesno(
|
||||||
heading="Playback mode change detected",
|
heading="Playback mode change detected",
|
||||||
line1=(
|
line1=(
|
||||||
|
@ -56,17 +56,17 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
utils.reset()'''
|
utils.reset()'''
|
||||||
|
|
||||||
currentLog = utils.settings('logLevel')
|
currentLog = utils.settings('logLevel')
|
||||||
if utils.window('emby_logLevel') != currentLog:
|
if utils.window('plex_logLevel') != currentLog:
|
||||||
# The log level changed, set new prop
|
# The log level changed, set new prop
|
||||||
self.logMsg("New log level: %s" % currentLog, 1)
|
self.logMsg("New log level: %s" % currentLog, 1)
|
||||||
utils.window('emby_logLevel', value=currentLog)
|
utils.window('plex_logLevel', value=currentLog)
|
||||||
|
|
||||||
|
@utils.CatchExceptions(warnuser=False)
|
||||||
def onNotification(self, sender, method, data):
|
def onNotification(self, sender, method, data):
|
||||||
if method not in ("Playlist.OnAdd"):
|
|
||||||
self.logMsg("Method: %s Data: %s" % (method, data), 1)
|
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
data = json.loads(data, 'utf-8')
|
data = json.loads(data, 'utf-8')
|
||||||
|
self.logMsg("Method: %s Data: %s" % (method, data), 1)
|
||||||
|
|
||||||
if method == "Player.OnPlay":
|
if method == "Player.OnPlay":
|
||||||
self.PlayBackStart(data)
|
self.PlayBackStart(data)
|
||||||
|
@ -82,13 +82,13 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
item = data.get('item')
|
item = data.get('item')
|
||||||
try:
|
try:
|
||||||
kodiid = item['id']
|
kodiid = item['id']
|
||||||
type = item['type']
|
item_type = item['type']
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
self.logMsg("Item is invalid for playstate update.", 1)
|
self.logMsg("Item is invalid for playstate update.", 1)
|
||||||
else:
|
else:
|
||||||
# Send notification to the server.
|
# Send notification to the server.
|
||||||
with embydb.GetEmbyDB() as emby_db:
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
|
emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type)
|
||||||
try:
|
try:
|
||||||
itemid = emby_dbitem[0]
|
itemid = emby_dbitem[0]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -137,14 +137,19 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
|
|
||||||
url = "{server}/emby/Items/%s?format=json" % itemid
|
url = "{server}/emby/Items/%s?format=json" % itemid
|
||||||
self.logMsg("Deleting request: %s" % itemid)
|
self.logMsg("Deleting request: %s" % itemid)
|
||||||
doUtils.downloadUrl(url, type="DELETE")
|
doUtils.downloadUrl(url, action_type="DELETE")
|
||||||
finally:
|
finally:
|
||||||
embycursor.close()'''
|
embycursor.close()'''
|
||||||
|
|
||||||
elif method == "System.OnWake":
|
elif method == "System.OnWake":
|
||||||
# Allow network to wake up
|
# Allow network to wake up
|
||||||
xbmc.sleep(10000)
|
xbmc.sleep(10000)
|
||||||
utils.window('emby_onWake', value="true")
|
utils.window('plex_onWake', value="true")
|
||||||
|
|
||||||
|
elif method == "GUI.OnScreensaverDeactivated":
|
||||||
|
if utils.settings('dbSyncScreensaver') == "true":
|
||||||
|
xbmc.sleep(5000)
|
||||||
|
utils.window('plex_runLibScan', value="full")
|
||||||
|
|
||||||
elif method == "Playlist.OnClear":
|
elif method == "Playlist.OnClear":
|
||||||
pass
|
pass
|
||||||
|
@ -155,7 +160,6 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
"""
|
"""
|
||||||
log = self.logMsg
|
log = self.logMsg
|
||||||
window = utils.window
|
window = utils.window
|
||||||
|
|
||||||
# Get currently playing file - can take a while. Will be utf-8!
|
# Get currently playing file - can take a while. Will be utf-8!
|
||||||
try:
|
try:
|
||||||
currentFile = self.xbmcplayer.getPlayingFile()
|
currentFile = self.xbmcplayer.getPlayingFile()
|
||||||
|
@ -173,53 +177,77 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
count += 1
|
count += 1
|
||||||
log("Currently playing file is: %s" % currentFile.decode('utf-8'), 1)
|
# Just to be on the safe side
|
||||||
|
currentFile = utils.tryDecode(currentFile)
|
||||||
|
log("Currently playing file is: %s" % currentFile, 1)
|
||||||
|
|
||||||
# Try to get a Kodi ID
|
# Get the type of media we're playing
|
||||||
item = data.get('item')
|
|
||||||
try:
|
try:
|
||||||
type = item['type']
|
typus = data['item']['type']
|
||||||
except:
|
except (TypeError, KeyError):
|
||||||
log("Item is invalid for PMS playstate update.", 0)
|
log("Item is invalid for PMS playstate update.", 0)
|
||||||
return
|
return
|
||||||
try:
|
log("Playing itemtype is (or appears to be): %s" % typus, 1)
|
||||||
kodiid = item['id']
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
itemType = window("emby_%s.type" % currentFile)
|
|
||||||
log("No kodi id passed. Playing itemtype is: %s" % itemType, 1)
|
|
||||||
if itemType in ('movie', 'episode'):
|
|
||||||
# Window was setup by PKC and is NOT a trailer ('clip')
|
|
||||||
with kodidb.GetKodiDB('video') as kodi_db:
|
|
||||||
kodiid = kodi_db.getIdFromTitle(data.get('item'))
|
|
||||||
if kodiid is None:
|
|
||||||
log("Skip playstate update. No unique Kodi title found"
|
|
||||||
" for %s" % data.get('item'), 0)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
log("Item is invalid for PMS playstate update.", 0)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get Plex' item id
|
# Try to get a Kodi ID
|
||||||
with embydb.GetEmbyDB() as emby_db:
|
# If PKC was used - native paths, not direct paths
|
||||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
|
plexid = utils.window('emby_%s.itemid'
|
||||||
try:
|
% utils.tryEncode(currentFile))
|
||||||
plexid = emby_dbitem[0]
|
# Get rid of the '' if the window property was not set
|
||||||
except TypeError:
|
plexid = None if not plexid else plexid
|
||||||
log("No Plex id returned for kodiid %s" % kodiid, 0)
|
kodiid = None
|
||||||
return
|
if plexid is None:
|
||||||
log("Found Plex id %s for Kodi id %s" % (plexid, kodiid), 1)
|
log('Did not get Plex id from window properties', 1)
|
||||||
|
try:
|
||||||
|
kodiid = data['item']['id']
|
||||||
|
except (TypeError, KeyError):
|
||||||
|
log('Did not get a Kodi id from Kodi, darn', 1)
|
||||||
|
# For direct paths, if we're not streaming something
|
||||||
|
# When using Widgets, Kodi doesn't tell us shit so we need this hack
|
||||||
|
if (kodiid is None and plexid is None and typus != 'song'
|
||||||
|
and not currentFile.startswith('http')):
|
||||||
|
try:
|
||||||
|
filename = currentFile.rsplit('/', 1)[1]
|
||||||
|
path = currentFile.rsplit('/', 1)[0] + '/'
|
||||||
|
except IndexError:
|
||||||
|
filename = currentFile.rsplit('\\', 1)[1]
|
||||||
|
path = currentFile.rsplit('\\', 1)[0] + '\\'
|
||||||
|
log('Trying to figure out playing item from filename: %s and '
|
||||||
|
'path: %s' % (filename, path), 1)
|
||||||
|
with kodidb.GetKodiDB('video') as kodi_db:
|
||||||
|
try:
|
||||||
|
kodiid, typus = kodi_db.getIdFromFilename(filename, path)
|
||||||
|
except TypeError:
|
||||||
|
log('Aborting playback report', 1)
|
||||||
|
return
|
||||||
|
|
||||||
|
if plexid is None:
|
||||||
|
# Get Plex' item id
|
||||||
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
|
emby_dbitem = emby_db.getItem_byKodiId(kodiid, typus)
|
||||||
|
try:
|
||||||
|
plexid = emby_dbitem[0]
|
||||||
|
except TypeError:
|
||||||
|
log("No Plex id returned for kodiid %s" % kodiid, 1)
|
||||||
|
log('Aborting playback report', 1)
|
||||||
|
return
|
||||||
|
log("Found Plex id %s for Kodi id %s for type %s"
|
||||||
|
% (plexid, kodiid, typus), 1)
|
||||||
|
|
||||||
# Set some stuff if Kodi initiated playback
|
# Set some stuff if Kodi initiated playback
|
||||||
if ((utils.settings('useDirectPaths') == "1" and not type == "song") or
|
if ((utils.settings('useDirectPaths') == "1" and not typus == "song")
|
||||||
(type == "song" and utils.settings('enableMusic') == "true")):
|
or
|
||||||
if self.StartDirectPath(plexid, type, currentFile) is False:
|
(typus == "song" and utils.settings('enableMusic') == "true")):
|
||||||
|
if self.StartDirectPath(plexid,
|
||||||
|
typus,
|
||||||
|
utils.tryEncode(currentFile)) is False:
|
||||||
log('Could not initiate monitoring; aborting', -1)
|
log('Could not initiate monitoring; aborting', -1)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Save currentFile for cleanup later and to be able to access refs
|
# Save currentFile for cleanup later and to be able to access refs
|
||||||
window('plex_lastPlayedFiled', value=currentFile.decode('utf-8'))
|
window('plex_lastPlayedFiled', value=currentFile)
|
||||||
window('Plex_currently_playing_itemid', value=plexid)
|
window('Plex_currently_playing_itemid', value=plexid)
|
||||||
window("emby_%s.itemid" % currentFile, value=plexid)
|
window("emby_%s.itemid" % utils.tryEncode(currentFile), value=plexid)
|
||||||
log('Finish playback startup', 1)
|
log('Finish playback startup', 1)
|
||||||
|
|
||||||
def StartDirectPath(self, plexid, type, currentFile):
|
def StartDirectPath(self, plexid, type, currentFile):
|
||||||
|
|
|
@ -255,7 +255,7 @@ class ThreadedShowSyncInfo(Thread):
|
||||||
|
|
||||||
@utils.logging
|
@utils.logging
|
||||||
@utils.ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
|
@utils.ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
|
||||||
@utils.ThreadMethodsAdditionalStop('emby_shouldStop')
|
@utils.ThreadMethodsAdditionalStop('plex_shouldStop')
|
||||||
@utils.ThreadMethods
|
@utils.ThreadMethods
|
||||||
class LibrarySync(Thread):
|
class LibrarySync(Thread):
|
||||||
"""
|
"""
|
||||||
|
@ -287,7 +287,6 @@ class LibrarySync(Thread):
|
||||||
self.vnodes = videonodes.VideoNodes()
|
self.vnodes = videonodes.VideoNodes()
|
||||||
self.dialog = xbmcgui.Dialog()
|
self.dialog = xbmcgui.Dialog()
|
||||||
|
|
||||||
self.syncThreadNumber = int(utils.settings('syncThreadNumber'))
|
|
||||||
self.installSyncDone = True if \
|
self.installSyncDone = True if \
|
||||||
utils.settings('SyncInstallRunDone') == 'true' else False
|
utils.settings('SyncInstallRunDone') == 'true' else False
|
||||||
self.showDbSync = True if \
|
self.showDbSync = True if \
|
||||||
|
@ -298,8 +297,8 @@ class LibrarySync(Thread):
|
||||||
'enableBackgroundSync') == "true" else False
|
'enableBackgroundSync') == "true" else False
|
||||||
self.limitindex = int(utils.settings('limitindex'))
|
self.limitindex = int(utils.settings('limitindex'))
|
||||||
|
|
||||||
if utils.settings('emby_pathverified') == 'true':
|
if utils.settings('plex_pathverified') == 'true':
|
||||||
utils.window('emby_pathverified', value='true')
|
utils.window('plex_pathverified', value='true')
|
||||||
|
|
||||||
# Just in case a time sync goes wrong
|
# Just in case a time sync goes wrong
|
||||||
self.timeoffset = int(utils.settings('kodiplextimeoffset'))
|
self.timeoffset = int(utils.settings('kodiplextimeoffset'))
|
||||||
|
@ -477,9 +476,6 @@ class LibrarySync(Thread):
|
||||||
# Add sources
|
# Add sources
|
||||||
utils.sourcesXML()
|
utils.sourcesXML()
|
||||||
|
|
||||||
# Ensure that DBs exist if called for very first time
|
|
||||||
self.initializeDBs()
|
|
||||||
|
|
||||||
# Set views. Abort if unsuccessful
|
# Set views. Abort if unsuccessful
|
||||||
if not self.maintainViews():
|
if not self.maintainViews():
|
||||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
||||||
|
@ -507,7 +503,7 @@ class LibrarySync(Thread):
|
||||||
if self.enableMusic:
|
if self.enableMusic:
|
||||||
xbmc.executebuiltin('UpdateLibrary(music)')
|
xbmc.executebuiltin('UpdateLibrary(music)')
|
||||||
|
|
||||||
utils.window('emby_initialScan', clear=True)
|
utils.window('plex_initialScan', clear=True)
|
||||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
||||||
utils.setScreensaver(value=screensaver)
|
utils.setScreensaver(value=screensaver)
|
||||||
if utils.window('plex_scancrashed') == 'true':
|
if utils.window('plex_scancrashed') == 'true':
|
||||||
|
@ -516,7 +512,7 @@ class LibrarySync(Thread):
|
||||||
utils.window('plex_scancrashed', clear=True)
|
utils.window('plex_scancrashed', clear=True)
|
||||||
elif utils.window('plex_scancrashed') == '401':
|
elif utils.window('plex_scancrashed') == '401':
|
||||||
utils.window('plex_scancrashed', clear=True)
|
utils.window('plex_scancrashed', clear=True)
|
||||||
if utils.window('emby_serverStatus') not in ('401', 'Auth'):
|
if utils.window('plex_serverStatus') not in ('401', 'Auth'):
|
||||||
# Plex server had too much and returned ERROR
|
# Plex server had too much and returned ERROR
|
||||||
self.dialog.ok(self.addonName, self.__language__(39409))
|
self.dialog.ok(self.addonName, self.__language__(39409))
|
||||||
|
|
||||||
|
@ -536,7 +532,7 @@ class LibrarySync(Thread):
|
||||||
folder = folderItem.attrib
|
folder = folderItem.attrib
|
||||||
mediatype = folder['type']
|
mediatype = folder['type']
|
||||||
# Only process supported formats
|
# Only process supported formats
|
||||||
if mediatype not in ('movie', 'show', 'artist'):
|
if mediatype not in ('movie', 'show', 'artist', 'photo'):
|
||||||
return totalnodes
|
return totalnodes
|
||||||
|
|
||||||
# Prevent duplicate for nodes of the same type
|
# Prevent duplicate for nodes of the same type
|
||||||
|
@ -683,18 +679,20 @@ class LibrarySync(Thread):
|
||||||
self.nodes = {
|
self.nodes = {
|
||||||
'movie': [],
|
'movie': [],
|
||||||
'show': [],
|
'show': [],
|
||||||
'artist': []
|
'artist': [],
|
||||||
|
'photo': []
|
||||||
}
|
}
|
||||||
self.playlists = {
|
self.playlists = {
|
||||||
'movie': [],
|
'movie': [],
|
||||||
'show': [],
|
'show': [],
|
||||||
'artist': []
|
'artist': [],
|
||||||
|
'photo': []
|
||||||
}
|
}
|
||||||
self.sorted_views = []
|
self.sorted_views = []
|
||||||
|
|
||||||
for view in sections:
|
for view in sections:
|
||||||
itemType = view.attrib['type']
|
itemType = view.attrib['type']
|
||||||
if itemType in ('movie', 'show'): # and NOT artist for now
|
if itemType in ('movie', 'show', 'photo'): # NOT artist for now
|
||||||
self.sorted_views.append(view.attrib['title'])
|
self.sorted_views.append(view.attrib['title'])
|
||||||
self.logMsg('Sorted views: %s' % self.sorted_views, 1)
|
self.logMsg('Sorted views: %s' % self.sorted_views, 1)
|
||||||
|
|
||||||
|
@ -733,15 +731,15 @@ class LibrarySync(Thread):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Save total
|
# Save total
|
||||||
utils.window('Emby.nodes.total', str(totalnodes))
|
utils.window('Plex.nodes.total', str(totalnodes))
|
||||||
|
|
||||||
# Reopen DB connection to ensure that changes were commited before
|
# Reopen DB connection to ensure that changes were commited before
|
||||||
with embydb.GetEmbyDB() as emby_db:
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
# update views for all:
|
|
||||||
self.views = emby_db.getAllViewInfo()
|
|
||||||
self.logMsg("Removing views: %s" % self.old_views, 1)
|
self.logMsg("Removing views: %s" % self.old_views, 1)
|
||||||
for view in self.old_views:
|
for view in self.old_views:
|
||||||
emby_db.removeView(view)
|
emby_db.removeView(view)
|
||||||
|
# update views for all:
|
||||||
|
self.views = emby_db.getAllViewInfo()
|
||||||
|
|
||||||
self.logMsg("Finished processing views. Views saved: %s"
|
self.logMsg("Finished processing views. Views saved: %s"
|
||||||
% self.views, 1)
|
% self.views, 1)
|
||||||
|
@ -847,16 +845,15 @@ class LibrarySync(Thread):
|
||||||
# Populate queue: GetMetadata
|
# Populate queue: GetMetadata
|
||||||
for updateItem in self.updatelist:
|
for updateItem in self.updatelist:
|
||||||
getMetadataQueue.put(updateItem)
|
getMetadataQueue.put(updateItem)
|
||||||
# Spawn GetMetadata threads for downloading
|
# Spawn GetMetadata thread for downloading
|
||||||
threads = []
|
threads = []
|
||||||
for i in range(min(self.syncThreadNumber, itemNumber)):
|
thread = ThreadedGetMetadata(getMetadataQueue,
|
||||||
thread = ThreadedGetMetadata(getMetadataQueue,
|
processMetadataQueue,
|
||||||
processMetadataQueue,
|
getMetadataLock,
|
||||||
getMetadataLock,
|
processMetadataLock)
|
||||||
processMetadataLock)
|
thread.setDaemon(True)
|
||||||
thread.setDaemon(True)
|
thread.start()
|
||||||
thread.start()
|
threads.append(thread)
|
||||||
threads.append(thread)
|
|
||||||
self.logMsg("%s download threads spawned" % len(threads), 1)
|
self.logMsg("%s download threads spawned" % len(threads), 1)
|
||||||
# Spawn one more thread to process Metadata, once downloaded
|
# Spawn one more thread to process Metadata, once downloaded
|
||||||
thread = ThreadedProcessMetadata(processMetadataQueue,
|
thread = ThreadedProcessMetadata(processMetadataQueue,
|
||||||
|
@ -1347,11 +1344,18 @@ class LibrarySync(Thread):
|
||||||
# We haven't waited long enough for the PMS to finish
|
# We haven't waited long enough for the PMS to finish
|
||||||
# processing the item. Do it later
|
# processing the item. Do it later
|
||||||
continue
|
continue
|
||||||
if item['state'] == 5:
|
if item['state'] == 9:
|
||||||
if self.process_newitems(item) is True:
|
successful = self.process_deleteditems(item)
|
||||||
deleteListe.append(i)
|
else:
|
||||||
elif item['state'] == 9:
|
successful = self.process_newitems(item)
|
||||||
if self.process_deleteditems(item) is True:
|
if successful is True:
|
||||||
|
deleteListe.append(i)
|
||||||
|
else:
|
||||||
|
# Safety net if we can't process an item
|
||||||
|
item['attempt'] += 1
|
||||||
|
if item['attempt'] > 3:
|
||||||
|
self.logMsg('Repeatetly could not process item %s, abort'
|
||||||
|
% item, -1)
|
||||||
deleteListe.append(i)
|
deleteListe.append(i)
|
||||||
|
|
||||||
# Get rid of the items we just processed
|
# Get rid of the items we just processed
|
||||||
|
@ -1423,15 +1427,23 @@ class LibrarySync(Thread):
|
||||||
"processing queue" for later
|
"processing queue" for later
|
||||||
"""
|
"""
|
||||||
for item in data:
|
for item in data:
|
||||||
state = item.get('state')
|
|
||||||
typus = item.get('type')
|
typus = item.get('type')
|
||||||
if state == 9 or (state == 5 and typus in (1, 4, 10)):
|
state = item.get('state')
|
||||||
self.itemsToProcess.append({
|
if state == 9 or typus in (1, 4, 10):
|
||||||
'state': state,
|
itemId = item.get('itemID')
|
||||||
'type': typus,
|
# Have we already added this element?
|
||||||
'ratingKey': item.get('itemID'),
|
for existingItem in self.itemsToProcess:
|
||||||
'timestamp': utils.getUnixTimestamp()
|
if existingItem['ratingKey'] == itemId:
|
||||||
})
|
break
|
||||||
|
else:
|
||||||
|
# Haven't added this element to the queue yet
|
||||||
|
self.itemsToProcess.append({
|
||||||
|
'state': state,
|
||||||
|
'type': typus,
|
||||||
|
'ratingKey': itemId,
|
||||||
|
'timestamp': utils.getUnixTimestamp(),
|
||||||
|
'attempt': 0
|
||||||
|
})
|
||||||
|
|
||||||
def process_playing(self, data):
|
def process_playing(self, data):
|
||||||
"""
|
"""
|
||||||
|
@ -1508,14 +1520,19 @@ class LibrarySync(Thread):
|
||||||
userdata = API.getUserData()
|
userdata = API.getUserData()
|
||||||
currSess['duration'] = userdata['Runtime']
|
currSess['duration'] = userdata['Runtime']
|
||||||
currSess['viewCount'] = userdata['PlayCount']
|
currSess['viewCount'] = userdata['PlayCount']
|
||||||
|
# Sometimes, Plex tells us resume points in milliseconds and
|
||||||
|
# not in seconds - thank you very much!
|
||||||
|
if item.get('viewOffset') > currSess['duration']:
|
||||||
|
resume = item.get('viewOffset') / 1000
|
||||||
|
else:
|
||||||
|
resume = item.get('viewOffset')
|
||||||
# Append to list that we need to process
|
# Append to list that we need to process
|
||||||
items.append({
|
items.append({
|
||||||
'ratingKey': ratingKey,
|
'ratingKey': ratingKey,
|
||||||
'kodi_id': kodiInfo[0],
|
'kodi_id': kodiInfo[0],
|
||||||
'file_id': kodiInfo[1],
|
'file_id': kodiInfo[1],
|
||||||
'kodi_type': kodiInfo[4],
|
'kodi_type': kodiInfo[4],
|
||||||
'viewOffset': PF.ConvertPlexToKodiTime(
|
'viewOffset': resume,
|
||||||
item.get('viewOffset')),
|
|
||||||
'state': state,
|
'state': state,
|
||||||
'duration': currSess['duration'],
|
'duration': currSess['duration'],
|
||||||
'viewCount': currSess['viewCount'],
|
'viewCount': currSess['viewCount'],
|
||||||
|
@ -1536,7 +1553,7 @@ class LibrarySync(Thread):
|
||||||
try:
|
try:
|
||||||
self.run_internal()
|
self.run_internal()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
utils.window('emby_dbScan', clear=True)
|
utils.window('plex_dbScan', clear=True)
|
||||||
self.logMsg('LibrarySync thread crashed', -1)
|
self.logMsg('LibrarySync thread crashed', -1)
|
||||||
self.logMsg('Error message: %s' % e, -1)
|
self.logMsg('Error message: %s' % e, -1)
|
||||||
import traceback
|
import traceback
|
||||||
|
@ -1575,6 +1592,9 @@ class LibrarySync(Thread):
|
||||||
|
|
||||||
log("---===### Starting LibrarySync ###===---", 0)
|
log("---===### Starting LibrarySync ###===---", 0)
|
||||||
|
|
||||||
|
# Ensure that DBs exist if called for very first time
|
||||||
|
self.initializeDBs()
|
||||||
|
|
||||||
if self.enableMusic:
|
if self.enableMusic:
|
||||||
utils.advancedSettingsXML()
|
utils.advancedSettingsXML()
|
||||||
|
|
||||||
|
@ -1589,10 +1609,10 @@ class LibrarySync(Thread):
|
||||||
return
|
return
|
||||||
xbmc.sleep(1000)
|
xbmc.sleep(1000)
|
||||||
|
|
||||||
if (window('emby_dbCheck') != "true" and installSyncDone):
|
if (window('plex_dbCheck') != "true" and installSyncDone):
|
||||||
# Verify the validity of the database
|
# Verify the validity of the database
|
||||||
currentVersion = settings('dbCreatedWithVersion')
|
currentVersion = settings('dbCreatedWithVersion')
|
||||||
minVersion = window('emby_minDBVersion')
|
minVersion = window('plex_minDBVersion')
|
||||||
|
|
||||||
if not self.compareDBVersion(currentVersion, minVersion):
|
if not self.compareDBVersion(currentVersion, minVersion):
|
||||||
log("Db version out of date: %s minimum version required: "
|
log("Db version out of date: %s minimum version required: "
|
||||||
|
@ -1609,7 +1629,7 @@ class LibrarySync(Thread):
|
||||||
utils.reset()
|
utils.reset()
|
||||||
break
|
break
|
||||||
|
|
||||||
window('emby_dbCheck', value="true")
|
window('plex_dbCheck', value="true")
|
||||||
|
|
||||||
if not startupComplete:
|
if not startupComplete:
|
||||||
# Also runs when first installed
|
# Also runs when first installed
|
||||||
|
@ -1619,15 +1639,15 @@ class LibrarySync(Thread):
|
||||||
# Database does not exists
|
# Database does not exists
|
||||||
log("The current Kodi version is incompatible "
|
log("The current Kodi version is incompatible "
|
||||||
"to know which Kodi versions are supported.", -1)
|
"to know which Kodi versions are supported.", -1)
|
||||||
log('Current Kodi version: %s' % xbmc.getInfoLabel(
|
log('Current Kodi version: %s' % utils.tryDecode(
|
||||||
'System.BuildVersion').decode('utf-8'))
|
xbmc.getInfoLabel('System.BuildVersion')))
|
||||||
# "Current Kodi version is unsupported, cancel lib sync"
|
# "Current Kodi version is unsupported, cancel lib sync"
|
||||||
self.dialog.ok(heading=self.addonName,
|
self.dialog.ok(heading=self.addonName,
|
||||||
line1=string(39403))
|
line1=string(39403))
|
||||||
break
|
break
|
||||||
|
|
||||||
# Run start up sync
|
# Run start up sync
|
||||||
window('emby_dbScan', value="true")
|
window('plex_dbScan', value="true")
|
||||||
log("Db version: %s" % settings('dbCreatedWithVersion'), 0)
|
log("Db version: %s" % settings('dbCreatedWithVersion'), 0)
|
||||||
lastTimeSync = utils.getUnixTimestamp()
|
lastTimeSync = utils.getUnixTimestamp()
|
||||||
self.syncPMStime()
|
self.syncPMStime()
|
||||||
|
@ -1635,7 +1655,7 @@ class LibrarySync(Thread):
|
||||||
lastSync = utils.getUnixTimestamp()
|
lastSync = utils.getUnixTimestamp()
|
||||||
librarySync = fullSync()
|
librarySync = fullSync()
|
||||||
# Initialize time offset Kodi - PMS
|
# Initialize time offset Kodi - PMS
|
||||||
window('emby_dbScan', clear=True)
|
window('plex_dbScan', clear=True)
|
||||||
if librarySync:
|
if librarySync:
|
||||||
log("Initial start-up full sync successful", 0)
|
log("Initial start-up full sync successful", 0)
|
||||||
startupComplete = True
|
startupComplete = True
|
||||||
|
@ -1655,23 +1675,23 @@ class LibrarySync(Thread):
|
||||||
break
|
break
|
||||||
|
|
||||||
# Currently no db scan, so we can start a new scan
|
# Currently no db scan, so we can start a new scan
|
||||||
elif window('emby_dbScan') != "true":
|
elif window('plex_dbScan') != "true":
|
||||||
# Full scan was requested from somewhere else, e.g. userclient
|
# Full scan was requested from somewhere else, e.g. userclient
|
||||||
if window('plex_runLibScan') in ("full", "repair"):
|
if window('plex_runLibScan') in ("full", "repair"):
|
||||||
log('Full library scan requested, starting', 0)
|
log('Full library scan requested, starting', 0)
|
||||||
window('emby_dbScan', value="true")
|
window('plex_dbScan', value="true")
|
||||||
if window('plex_runLibScan') == "full":
|
if window('plex_runLibScan') == "full":
|
||||||
fullSync()
|
fullSync()
|
||||||
elif window('plex_runLibScan') == "repair":
|
elif window('plex_runLibScan') == "repair":
|
||||||
fullSync(repair=True)
|
fullSync(repair=True)
|
||||||
window('plex_runLibScan', clear=True)
|
window('plex_runLibScan', clear=True)
|
||||||
window('emby_dbScan', clear=True)
|
window('plex_dbScan', clear=True)
|
||||||
# Full library sync finished
|
# Full library sync finished
|
||||||
self.showKodiNote(string(39407), forced=True)
|
self.showKodiNote(string(39407), forced=False)
|
||||||
# Reset views was requested from somewhere else
|
# Reset views was requested from somewhere else
|
||||||
elif window('plex_runLibScan') == "views":
|
elif window('plex_runLibScan') == "views":
|
||||||
log('Refresh playlist and nodes requested, starting', 0)
|
log('Refresh playlist and nodes requested, starting', 0)
|
||||||
window('emby_dbScan', value="true")
|
window('plex_dbScan', value="true")
|
||||||
window('plex_runLibScan', clear=True)
|
window('plex_runLibScan', clear=True)
|
||||||
|
|
||||||
# First remove playlists
|
# First remove playlists
|
||||||
|
@ -1691,36 +1711,36 @@ class LibrarySync(Thread):
|
||||||
self.showKodiNote(string(39406),
|
self.showKodiNote(string(39406),
|
||||||
forced=True,
|
forced=True,
|
||||||
icon="error")
|
icon="error")
|
||||||
window('emby_dbScan', clear=True)
|
window('plex_dbScan', clear=True)
|
||||||
else:
|
else:
|
||||||
now = utils.getUnixTimestamp()
|
now = utils.getUnixTimestamp()
|
||||||
if (now - lastSync > fullSyncInterval and
|
if (now - lastSync > fullSyncInterval and
|
||||||
not xbmcplayer.isPlaying()):
|
not xbmcplayer.isPlaying()):
|
||||||
lastSync = now
|
lastSync = now
|
||||||
log('Doing scheduled full library scan', 1)
|
log('Doing scheduled full library scan', 1)
|
||||||
window('emby_dbScan', value="true")
|
window('plex_dbScan', value="true")
|
||||||
if fullSync() is False and not threadStopped():
|
if fullSync() is False and not threadStopped():
|
||||||
log('Could not finish scheduled full sync', -1)
|
log('Could not finish scheduled full sync', -1)
|
||||||
self.showKodiNote(string(39410),
|
self.showKodiNote(string(39410),
|
||||||
forced=True,
|
forced=True,
|
||||||
icon='error')
|
icon='error')
|
||||||
window('emby_dbScan', clear=True)
|
window('plex_dbScan', clear=True)
|
||||||
# Full library sync finished
|
# Full library sync finished
|
||||||
self.showKodiNote(string(39407), forced=False)
|
self.showKodiNote(string(39407), forced=False)
|
||||||
elif now - lastTimeSync > oneDay:
|
elif now - lastTimeSync > oneDay:
|
||||||
lastTimeSync = now
|
lastTimeSync = now
|
||||||
log('Starting daily time sync', 0)
|
log('Starting daily time sync', 0)
|
||||||
window('emby_dbScan', value="true")
|
window('plex_dbScan', value="true")
|
||||||
self.syncPMStime()
|
self.syncPMStime()
|
||||||
window('emby_dbScan', clear=True)
|
window('plex_dbScan', clear=True)
|
||||||
elif enableBackgroundSync:
|
elif enableBackgroundSync:
|
||||||
# Check back whether we should process something
|
# Check back whether we should process something
|
||||||
# Only do this once every 10 seconds
|
# Only do this once every 10 seconds
|
||||||
if now - lastProcessing > 10:
|
if now - lastProcessing > 10:
|
||||||
lastProcessing = now
|
lastProcessing = now
|
||||||
window('emby_dbScan', value="true")
|
window('plex_dbScan', value="true")
|
||||||
processItems()
|
processItems()
|
||||||
window('emby_dbScan', clear=True)
|
window('plex_dbScan', clear=True)
|
||||||
# See if there is a PMS message we need to handle
|
# See if there is a PMS message we need to handle
|
||||||
try:
|
try:
|
||||||
message = queue.get(block=False)
|
message = queue.get(block=False)
|
||||||
|
@ -1729,10 +1749,10 @@ class LibrarySync(Thread):
|
||||||
continue
|
continue
|
||||||
# Got a message from PMS; process it
|
# Got a message from PMS; process it
|
||||||
else:
|
else:
|
||||||
window('emby_dbScan', value="true")
|
window('plex_dbScan', value="true")
|
||||||
processMessage(message)
|
processMessage(message)
|
||||||
queue.task_done()
|
queue.task_done()
|
||||||
window('emby_dbScan', clear=True)
|
window('plex_dbScan', clear=True)
|
||||||
# NO sleep!
|
# NO sleep!
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -32,7 +32,7 @@ def getRealFileName(filename, isTemp=False):
|
||||||
if os.path.supports_unicode_filenames:
|
if os.path.supports_unicode_filenames:
|
||||||
checkfile = filename
|
checkfile = filename
|
||||||
else:
|
else:
|
||||||
checkfile = filename.encode("utf-8")
|
checkfile = utils.tryEncode(filename)
|
||||||
|
|
||||||
# determine if our python module is able to access the file directly...
|
# determine if our python module is able to access the file directly...
|
||||||
if os.path.exists(checkfile):
|
if os.path.exists(checkfile):
|
||||||
|
@ -46,7 +46,7 @@ def getRealFileName(filename, isTemp=False):
|
||||||
else: filepart = filename.split("\\")[-1]
|
else: filepart = filename.split("\\")[-1]
|
||||||
tempfile = "special://temp/"+filepart
|
tempfile = "special://temp/"+filepart
|
||||||
xbmcvfs.copy(filename, tempfile)
|
xbmcvfs.copy(filename, tempfile)
|
||||||
filename = xbmc.translatePath(tempfile).decode("utf-8")
|
filename = utils.tryDecode(xbmc.translatePath(tempfile))
|
||||||
|
|
||||||
return (isTemp,filename)
|
return (isTemp,filename)
|
||||||
|
|
||||||
|
@ -65,14 +65,14 @@ def getEmbyRatingFromKodiRating(rating):
|
||||||
if (rating >= 5): favourite = True
|
if (rating >= 5): favourite = True
|
||||||
return(like, favourite, deletelike)
|
return(like, favourite, deletelike)
|
||||||
|
|
||||||
def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enableimportsongrating, enableexportsongrating, enableupdatesongrating):
|
def getAdditionalSongTags(plexid, plex_rating, API, kodicursor, emby_db, enableimportsongrating, enableexportsongrating, enableupdatesongrating):
|
||||||
|
|
||||||
emby = embyserver.Read_EmbyServer()
|
emby = embyserver.Read_EmbyServer()
|
||||||
|
|
||||||
previous_values = None
|
previous_values = None
|
||||||
filename = API.getFilePath()
|
filename = API.getFilePath()
|
||||||
rating = 0
|
rating = 0
|
||||||
emby_rating = int(round(emby_rating, 0))
|
plex_rating = int(round(plex_rating, 0))
|
||||||
|
|
||||||
#get file rating and comment tag from file itself.
|
#get file rating and comment tag from file itself.
|
||||||
if enableimportsongrating:
|
if enableimportsongrating:
|
||||||
|
@ -83,7 +83,7 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei
|
||||||
hasEmbeddedCover = False
|
hasEmbeddedCover = False
|
||||||
|
|
||||||
|
|
||||||
emby_dbitem = emby_db.getItem_byId(embyid)
|
emby_dbitem = emby_db.getItem_byId(plexid)
|
||||||
try:
|
try:
|
||||||
kodiid = emby_dbitem[0]
|
kodiid = emby_dbitem[0]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -100,44 +100,44 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei
|
||||||
if file_rating is None and currentvalue:
|
if file_rating is None and currentvalue:
|
||||||
return (currentvalue, comment, False)
|
return (currentvalue, comment, False)
|
||||||
elif file_rating is None and not currentvalue:
|
elif file_rating is None and not currentvalue:
|
||||||
return (emby_rating, comment, False)
|
return (plex_rating, comment, False)
|
||||||
|
|
||||||
logMsg("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue))
|
logMsg("getAdditionalSongTags --> plexid: %s - plex_rating: %s - file_rating: %s - current rating in kodidb: %s" %(plexid, plex_rating, file_rating, currentvalue))
|
||||||
|
|
||||||
updateFileRating = False
|
updateFileRating = False
|
||||||
updateEmbyRating = False
|
updateEmbyRating = False
|
||||||
|
|
||||||
if currentvalue != None:
|
if currentvalue != None:
|
||||||
# we need to translate the emby values...
|
# we need to translate the emby values...
|
||||||
if emby_rating == 1 and currentvalue == 2:
|
if plex_rating == 1 and currentvalue == 2:
|
||||||
emby_rating = 2
|
plex_rating = 2
|
||||||
if emby_rating == 3 and currentvalue == 4:
|
if plex_rating == 3 and currentvalue == 4:
|
||||||
emby_rating = 4
|
plex_rating = 4
|
||||||
|
|
||||||
#if updating rating into file is disabled, we ignore the rating in the file...
|
#if updating rating into file is disabled, we ignore the rating in the file...
|
||||||
if not enableupdatesongrating:
|
if not enableupdatesongrating:
|
||||||
file_rating = currentvalue
|
file_rating = currentvalue
|
||||||
#if convert emby likes/favourites convert to song rating is disabled, we ignore the emby rating...
|
#if convert emby likes/favourites convert to song rating is disabled, we ignore the emby rating...
|
||||||
if not enableexportsongrating:
|
if not enableexportsongrating:
|
||||||
emby_rating = currentvalue
|
plex_rating = currentvalue
|
||||||
|
|
||||||
if (emby_rating == file_rating) and (file_rating != currentvalue):
|
if (plex_rating == file_rating) and (file_rating != currentvalue):
|
||||||
#the rating has been updated from kodi itself, update change to both emby ands file
|
#the rating has been updated from kodi itself, update change to both emby ands file
|
||||||
rating = currentvalue
|
rating = currentvalue
|
||||||
updateFileRating = True
|
updateFileRating = True
|
||||||
updateEmbyRating = True
|
updateEmbyRating = True
|
||||||
elif (emby_rating != currentvalue) and (file_rating == currentvalue):
|
elif (plex_rating != currentvalue) and (file_rating == currentvalue):
|
||||||
#emby rating changed - update the file
|
#emby rating changed - update the file
|
||||||
rating = emby_rating
|
rating = plex_rating
|
||||||
updateFileRating = True
|
updateFileRating = True
|
||||||
elif (file_rating != currentvalue) and (emby_rating == currentvalue):
|
elif (file_rating != currentvalue) and (plex_rating == currentvalue):
|
||||||
#file rating was updated, sync change to emby
|
#file rating was updated, sync change to emby
|
||||||
rating = file_rating
|
rating = file_rating
|
||||||
updateEmbyRating = True
|
updateEmbyRating = True
|
||||||
elif (emby_rating != currentvalue) and (file_rating != currentvalue):
|
elif (plex_rating != currentvalue) and (file_rating != currentvalue):
|
||||||
#both ratings have changed (corner case) - the highest rating wins...
|
#both ratings have changed (corner case) - the highest rating wins...
|
||||||
if emby_rating > file_rating:
|
if plex_rating > file_rating:
|
||||||
rating = emby_rating
|
rating = plex_rating
|
||||||
updateFileRating = True
|
updateFileRating = True
|
||||||
else:
|
else:
|
||||||
rating = file_rating
|
rating = file_rating
|
||||||
|
@ -152,16 +152,16 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei
|
||||||
rating = file_rating
|
rating = file_rating
|
||||||
#determine if we should also send the rating to emby server
|
#determine if we should also send the rating to emby server
|
||||||
if enableexportsongrating:
|
if enableexportsongrating:
|
||||||
if emby_rating == 1 and file_rating == 2:
|
if plex_rating == 1 and file_rating == 2:
|
||||||
emby_rating = 2
|
plex_rating = 2
|
||||||
if emby_rating == 3 and file_rating == 4:
|
if plex_rating == 3 and file_rating == 4:
|
||||||
emby_rating = 4
|
plex_rating = 4
|
||||||
if emby_rating != file_rating:
|
if plex_rating != file_rating:
|
||||||
updateEmbyRating = True
|
updateEmbyRating = True
|
||||||
|
|
||||||
elif enableexportsongrating:
|
elif enableexportsongrating:
|
||||||
#set the initial rating to emby value
|
#set the initial rating to emby value
|
||||||
rating = emby_rating
|
rating = plex_rating
|
||||||
|
|
||||||
if updateFileRating and enableupdatesongrating:
|
if updateFileRating and enableupdatesongrating:
|
||||||
updateRatingToFile(rating, filename)
|
updateRatingToFile(rating, filename)
|
||||||
|
@ -169,8 +169,8 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei
|
||||||
if updateEmbyRating and enableexportsongrating:
|
if updateEmbyRating and enableexportsongrating:
|
||||||
# sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
|
# sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
|
||||||
like, favourite, deletelike = getEmbyRatingFromKodiRating(rating)
|
like, favourite, deletelike = getEmbyRatingFromKodiRating(rating)
|
||||||
utils.window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update
|
utils.window("ignore-update-%s" %plexid, "true") #set temp windows prop to ignore the update from webclient update
|
||||||
emby.updateUserRating(embyid, like, favourite, deletelike)
|
emby.updateUserRating(plexid, like, favourite, deletelike)
|
||||||
|
|
||||||
return (rating, comment, hasEmbeddedCover)
|
return (rating, comment, hasEmbeddedCover)
|
||||||
|
|
||||||
|
@ -193,6 +193,7 @@ def getSongTags(file):
|
||||||
if pic.type == 3 and pic.data:
|
if pic.type == 3 and pic.data:
|
||||||
#the file has an embedded cover
|
#the file has an embedded cover
|
||||||
hasEmbeddedCover = True
|
hasEmbeddedCover = True
|
||||||
|
break
|
||||||
if audio.get("rating"):
|
if audio.get("rating"):
|
||||||
rating = float(audio.get("rating")[0])
|
rating = float(audio.get("rating")[0])
|
||||||
#flac rating is 0-100 and needs to be converted to 0-5 range
|
#flac rating is 0-100 and needs to be converted to 0-5 range
|
||||||
|
@ -241,7 +242,7 @@ def updateRatingToFile(rating, file):
|
||||||
else: filepart = file.split("\\")[-1]
|
else: filepart = file.split("\\")[-1]
|
||||||
tempfile = "special://temp/"+filepart
|
tempfile = "special://temp/"+filepart
|
||||||
xbmcvfs.copy(file, tempfile)
|
xbmcvfs.copy(file, tempfile)
|
||||||
tempfile = xbmc.translatePath(tempfile).decode("utf-8")
|
tempfile = utils.tryDecode(xbmc.translatePath(tempfile))
|
||||||
|
|
||||||
logMsg( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile))
|
logMsg( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile))
|
||||||
|
|
||||||
|
|
|
@ -40,11 +40,13 @@ class PlaybackUtils():
|
||||||
|
|
||||||
self.artwork = artwork.Artwork()
|
self.artwork = artwork.Artwork()
|
||||||
self.emby = embyserver.Read_EmbyServer()
|
self.emby = embyserver.Read_EmbyServer()
|
||||||
self.pl = playlist.Playlist()
|
if self.API.getType() == 'track':
|
||||||
|
self.pl = playlist.Playlist(typus='music')
|
||||||
|
else:
|
||||||
|
self.pl = playlist.Playlist(typus='video')
|
||||||
|
|
||||||
def play(self, itemid, dbid=None):
|
def play(self, itemid, dbid=None):
|
||||||
|
|
||||||
log = self.logMsg
|
|
||||||
window = utils.window
|
window = utils.window
|
||||||
settings = utils.settings
|
settings = utils.settings
|
||||||
|
|
||||||
|
@ -56,7 +58,7 @@ class PlaybackUtils():
|
||||||
listitem = xbmcgui.ListItem()
|
listitem = xbmcgui.ListItem()
|
||||||
playutils = putils.PlayUtils(item[0])
|
playutils = putils.PlayUtils(item[0])
|
||||||
|
|
||||||
log("Play called.", 1)
|
self.logMsg("Play called.", 1)
|
||||||
playurl = playutils.getPlayUrl()
|
playurl = playutils.getPlayUrl()
|
||||||
if not playurl:
|
if not playurl:
|
||||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
||||||
|
@ -72,18 +74,18 @@ class PlaybackUtils():
|
||||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||||
'{server}%s' % item[0][0][0].attrib.get('key'))
|
'{server}%s' % item[0][0][0].attrib.get('key'))
|
||||||
if xml in (None, 401):
|
if xml in (None, 401):
|
||||||
log('Could not download %s'
|
self.logMsg('Could not download %s'
|
||||||
% item[0][0][0].attrib.get('key'), -1)
|
% item[0][0][0].attrib.get('key'), -1)
|
||||||
return xbmcplugin.setResolvedUrl(
|
return xbmcplugin.setResolvedUrl(
|
||||||
int(sys.argv[1]), False, listitem)
|
int(sys.argv[1]), False, listitem)
|
||||||
playurl = xml[0].attrib.get('key').encode('utf-8')
|
playurl = utils.tryEncode(xml[0].attrib.get('key'))
|
||||||
window('emby_%s.playmethod' % playurl, value='DirectStream')
|
window('emby_%s.playmethod' % playurl, value='DirectStream')
|
||||||
|
|
||||||
playmethod = window('emby_%s.playmethod' % playurl)
|
playmethod = window('emby_%s.playmethod' % playurl)
|
||||||
if playmethod == "Transcode":
|
if playmethod == "Transcode":
|
||||||
window('emby_%s.playmethod' % playurl, clear=True)
|
window('emby_%s.playmethod' % playurl, clear=True)
|
||||||
playurl = playutils.audioSubsPref(
|
playurl = utils.tryEncode(playutils.audioSubsPref(
|
||||||
listitem, playurl.decode('utf-8')).encode('utf-8')
|
listitem, utils.tryDecode(playurl)))
|
||||||
window('emby_%s.playmethod' % playurl, "Transcode")
|
window('emby_%s.playmethod' % playurl, "Transcode")
|
||||||
listitem.setPath(playurl)
|
listitem.setPath(playurl)
|
||||||
self.setProperties(playurl, listitem)
|
self.setProperties(playurl, listitem)
|
||||||
|
@ -92,18 +94,18 @@ class PlaybackUtils():
|
||||||
############### ORGANIZE CURRENT PLAYLIST ################
|
############### ORGANIZE CURRENT PLAYLIST ################
|
||||||
|
|
||||||
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
|
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
|
||||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
playlist = self.pl.playlist
|
||||||
startPos = max(playlist.getposition(), 0) # Can return -1
|
startPos = max(playlist.getposition(), 0) # Can return -1
|
||||||
sizePlaylist = playlist.size()
|
sizePlaylist = playlist.size()
|
||||||
self.currentPosition = startPos
|
self.currentPosition = startPos
|
||||||
|
|
||||||
propertiesPlayback = window('emby_playbackProps') == "true"
|
propertiesPlayback = window('plex_playbackProps') == "true"
|
||||||
introsPlaylist = False
|
introsPlaylist = False
|
||||||
dummyPlaylist = False
|
dummyPlaylist = False
|
||||||
|
|
||||||
log("Playlist start position: %s" % startPos, 1)
|
self.logMsg("Playlist start position: %s" % startPos, 2)
|
||||||
log("Playlist plugin position: %s" % self.currentPosition, 1)
|
self.logMsg("Playlist plugin position: %s" % self.currentPosition, 2)
|
||||||
log("Playlist size: %s" % sizePlaylist, 1)
|
self.logMsg("Playlist size: %s" % sizePlaylist, 2)
|
||||||
|
|
||||||
############### RESUME POINT ################
|
############### RESUME POINT ################
|
||||||
|
|
||||||
|
@ -113,12 +115,12 @@ class PlaybackUtils():
|
||||||
# Otherwise we get a loop.
|
# Otherwise we get a loop.
|
||||||
if not propertiesPlayback:
|
if not propertiesPlayback:
|
||||||
|
|
||||||
window('emby_playbackProps', value="true")
|
window('plex_playbackProps', value="true")
|
||||||
log("Setting up properties in playlist.", 1)
|
self.logMsg("Setting up properties in playlist.", 1)
|
||||||
|
|
||||||
if (not homeScreen and not seektime and
|
if (not homeScreen and not seektime and
|
||||||
window('emby_customPlaylist') != "true"):
|
window('plex_customplaylist') != "true"):
|
||||||
log("Adding dummy file to playlist.", 2)
|
self.logMsg("Adding dummy file to playlist.", 2)
|
||||||
dummyPlaylist = True
|
dummyPlaylist = True
|
||||||
playlist.add(playurl, listitem, index=startPos)
|
playlist.add(playurl, listitem, index=startPos)
|
||||||
# Remove the original item from playlist
|
# Remove the original item from playlist
|
||||||
|
@ -145,7 +147,7 @@ class PlaybackUtils():
|
||||||
if homeScreen and not seektime and not sizePlaylist:
|
if homeScreen and not seektime and not sizePlaylist:
|
||||||
# Extend our current playlist with the actual item to play
|
# Extend our current playlist with the actual item to play
|
||||||
# only if there's no playlist first
|
# only if there's no playlist first
|
||||||
log("Adding main item to playlist.", 1)
|
self.logMsg("Adding main item to playlist.", 1)
|
||||||
self.pl.addtoPlaylist(
|
self.pl.addtoPlaylist(
|
||||||
dbid,
|
dbid,
|
||||||
PF.GetKodiTypeFromPlex(API.getType()))
|
PF.GetKodiTypeFromPlex(API.getType()))
|
||||||
|
@ -166,7 +168,7 @@ class PlaybackUtils():
|
||||||
additionalListItem = xbmcgui.ListItem()
|
additionalListItem = xbmcgui.ListItem()
|
||||||
additionalPlayurl = playutils.getPlayUrl(
|
additionalPlayurl = playutils.getPlayUrl(
|
||||||
partNumber=counter)
|
partNumber=counter)
|
||||||
log("Adding additional part: %s" % counter, 1)
|
self.logMsg("Adding additional part: %s" % counter, 1)
|
||||||
|
|
||||||
self.setProperties(additionalPlayurl, additionalListItem)
|
self.setProperties(additionalPlayurl, additionalListItem)
|
||||||
self.setArtwork(additionalListItem)
|
self.setArtwork(additionalListItem)
|
||||||
|
@ -181,14 +183,14 @@ class PlaybackUtils():
|
||||||
if dummyPlaylist:
|
if dummyPlaylist:
|
||||||
# Added a dummy file to the playlist,
|
# Added a dummy file to the playlist,
|
||||||
# because the first item is going to fail automatically.
|
# because the first item is going to fail automatically.
|
||||||
log("Processed as a playlist. First item is skipped.", 1)
|
self.logMsg("Processed as a playlist. First item is skipped.", 1)
|
||||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
||||||
|
|
||||||
|
|
||||||
# We just skipped adding properties. Reset flag for next time.
|
# We just skipped adding properties. Reset flag for next time.
|
||||||
elif propertiesPlayback:
|
elif propertiesPlayback:
|
||||||
log("Resetting properties playback flag.", 2)
|
self.logMsg("Resetting properties playback flag.", 2)
|
||||||
window('emby_playbackProps', clear=True)
|
window('plex_playbackProps', clear=True)
|
||||||
|
|
||||||
#self.pl.verifyPlaylist()
|
#self.pl.verifyPlaylist()
|
||||||
########## SETUP MAIN ITEM ##########
|
########## SETUP MAIN ITEM ##########
|
||||||
|
@ -196,8 +198,8 @@ class PlaybackUtils():
|
||||||
# For transcoding only, ask for audio/subs pref
|
# For transcoding only, ask for audio/subs pref
|
||||||
if window('emby_%s.playmethod' % playurl) == "Transcode":
|
if window('emby_%s.playmethod' % playurl) == "Transcode":
|
||||||
window('emby_%s.playmethod' % playurl, clear=True)
|
window('emby_%s.playmethod' % playurl, clear=True)
|
||||||
playurl = playutils.audioSubsPref(
|
playurl = utils.tryEncode(playutils.audioSubsPref(
|
||||||
listitem, playurl.decode('utf-8')).encode('utf-8')
|
listitem, utils.tryDecode(playurl)))
|
||||||
window('emby_%s.playmethod' % playurl, value="Transcode")
|
window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||||
|
|
||||||
listitem.setPath(playurl)
|
listitem.setPath(playurl)
|
||||||
|
@ -205,19 +207,19 @@ class PlaybackUtils():
|
||||||
|
|
||||||
############### PLAYBACK ################
|
############### PLAYBACK ################
|
||||||
|
|
||||||
if homeScreen and seektime and window('emby_customPlaylist') != "true":
|
if homeScreen and seektime and window('plex_customplaylist') != "true":
|
||||||
log("Play as a widget item.", 1)
|
self.logMsg("Play as a widget item.", 1)
|
||||||
API.CreateListItemFromPlexItem(listitem)
|
API.CreateListItemFromPlexItem(listitem)
|
||||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||||
|
|
||||||
elif ((introsPlaylist and window('emby_customPlaylist') == "true") or
|
elif ((introsPlaylist and window('plex_customplaylist') == "true") or
|
||||||
(homeScreen and not sizePlaylist)):
|
(homeScreen and not sizePlaylist)):
|
||||||
# Playlist was created just now, play it.
|
# Playlist was created just now, play it.
|
||||||
log("Play playlist.", 1)
|
self.logMsg("Play playlist.", 1)
|
||||||
xbmc.Player().play(playlist, startpos=startPos)
|
xbmc.Player().play(playlist, startpos=startPos)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
log("Play as a regular item.", 1)
|
self.logMsg("Play as a regular item.", 1)
|
||||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||||
|
|
||||||
def AddTrailers(self, xml):
|
def AddTrailers(self, xml):
|
||||||
|
@ -268,17 +270,13 @@ class PlaybackUtils():
|
||||||
# Set all properties necessary for plugin path playback
|
# Set all properties necessary for plugin path playback
|
||||||
itemid = self.API.getRatingKey()
|
itemid = self.API.getRatingKey()
|
||||||
itemtype = self.API.getType()
|
itemtype = self.API.getType()
|
||||||
resume, runtime = self.API.getRuntime()
|
userdata = self.API.getUserData()
|
||||||
|
|
||||||
embyitem = "emby_%s" % playurl
|
embyitem = "emby_%s" % playurl
|
||||||
window('%s.runtime' % embyitem, value=str(runtime))
|
window('%s.runtime' % embyitem, value=str(userdata['Runtime']))
|
||||||
window('%s.type' % embyitem, value=itemtype)
|
window('%s.type' % embyitem, value=itemtype)
|
||||||
window('%s.itemid' % embyitem, value=itemid)
|
window('%s.itemid' % embyitem, value=itemid)
|
||||||
|
window('%s.playcount' % embyitem, value=str(userdata['PlayCount']))
|
||||||
# We need to keep track of playQueueItemIDs for Plex Companion
|
|
||||||
window('plex_%s.playQueueItemID'
|
|
||||||
% playurl, self.API.GetPlayQueueItemID())
|
|
||||||
window('plex_%s.guid' % playurl, self.API.getGuid())
|
|
||||||
|
|
||||||
if itemtype == "episode":
|
if itemtype == "episode":
|
||||||
window('%s.refreshid' % embyitem,
|
window('%s.refreshid' % embyitem,
|
||||||
|
|
|
@ -10,8 +10,8 @@ import xbmcgui
|
||||||
import utils
|
import utils
|
||||||
import clientinfo
|
import clientinfo
|
||||||
import downloadutils
|
import downloadutils
|
||||||
|
import embydb_functions as embydb
|
||||||
from urllib import urlencode
|
import kodidb_functions as kodidb
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -32,17 +32,10 @@ class Player(xbmc.Player):
|
||||||
|
|
||||||
self.clientInfo = clientinfo.ClientInfo()
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||||
self.xbmcplayer = xbmc.Player()
|
|
||||||
|
|
||||||
self.logMsg("Starting playback monitor.", 2)
|
xbmc.Player.__init__(self)
|
||||||
|
|
||||||
# Should we start notification or is this done by Plex Companion?
|
self.logMsg("Started playback monitor.", 2)
|
||||||
self.doNotify = False if (utils.settings('plexCompanion') == 'true') \
|
|
||||||
else True
|
|
||||||
if self.doNotify:
|
|
||||||
self.logMsg("PMS notifications not done by Plex Companion", 2)
|
|
||||||
else:
|
|
||||||
self.logMsg("PMS notifications done by Plex Companion", 2)
|
|
||||||
|
|
||||||
def GetPlayStats(self):
|
def GetPlayStats(self):
|
||||||
return self.playStats
|
return self.playStats
|
||||||
|
@ -51,15 +44,13 @@ class Player(xbmc.Player):
|
||||||
"""
|
"""
|
||||||
Window values need to have been set in Kodimonitor.py
|
Window values need to have been set in Kodimonitor.py
|
||||||
"""
|
"""
|
||||||
log = self.logMsg
|
|
||||||
window = utils.window
|
window = utils.window
|
||||||
# Will be called when xbmc starts playing a file
|
# Will be called when xbmc starts playing a file
|
||||||
xbmcplayer = self.xbmcplayer
|
|
||||||
self.stopAll()
|
self.stopAll()
|
||||||
|
|
||||||
# Get current file (in utf-8!)
|
# Get current file (in utf-8!)
|
||||||
try:
|
try:
|
||||||
currentFile = xbmcplayer.getPlayingFile()
|
currentFile = self.getPlayingFile()
|
||||||
xbmc.sleep(300)
|
xbmc.sleep(300)
|
||||||
except:
|
except:
|
||||||
currentFile = ""
|
currentFile = ""
|
||||||
|
@ -67,53 +58,65 @@ class Player(xbmc.Player):
|
||||||
while not currentFile:
|
while not currentFile:
|
||||||
xbmc.sleep(100)
|
xbmc.sleep(100)
|
||||||
try:
|
try:
|
||||||
currentFile = xbmcplayer.getPlayingFile()
|
currentFile = self.getPlayingFile()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
if count == 20:
|
if count == 20:
|
||||||
log("Cancelling playback report...", 1)
|
self.logMsg("Cancelling playback report...", 1)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
count += 1
|
count += 1
|
||||||
if not currentFile:
|
if not currentFile:
|
||||||
log('Error getting a currently playing file; abort reporting', -1)
|
self.logMsg('Error getting a currently playing file; abort '
|
||||||
|
'reporting', -1)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Save currentFile for cleanup later and for references
|
# Save currentFile for cleanup later and for references
|
||||||
self.currentFile = currentFile
|
self.currentFile = currentFile
|
||||||
window('plex_lastPlayedFiled', value=currentFile.decode('utf-8'))
|
window('plex_lastPlayedFiled', value=utils.tryDecode(currentFile))
|
||||||
# We may need to wait for info to be set in kodi monitor
|
# We may need to wait for info to be set in kodi monitor
|
||||||
itemId = window("emby_%s.itemid" % currentFile)
|
itemId = window("emby_%s.itemid" % currentFile)
|
||||||
count = 0
|
count = 0
|
||||||
while not itemId:
|
while not itemId:
|
||||||
xbmc.sleep(200)
|
xbmc.sleep(200)
|
||||||
itemId = window("emby_%s.itemid" % currentFile)
|
itemId = window("emby_%s.itemid" % currentFile)
|
||||||
# try 20 times or about 10 seconds
|
if count == 5:
|
||||||
if count == 20:
|
self.logMsg("Could not find itemId, cancelling playback "
|
||||||
log("Could not find itemId, cancelling playback report...", -1)
|
"report!", -1)
|
||||||
return
|
return
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
log("ONPLAYBACK_STARTED: %s itemid: %s"
|
self.logMsg("ONPLAYBACK_STARTED: %s itemid: %s"
|
||||||
% (currentFile.decode('utf-8'), itemId), 0)
|
% (utils.tryDecode(currentFile), itemId), 0)
|
||||||
|
|
||||||
embyitem = "emby_%s" % currentFile
|
embyitem = "emby_%s" % currentFile
|
||||||
runtime = window("%s.runtime" % embyitem)
|
runtime = window("%s.runtime" % embyitem)
|
||||||
refresh_id = window("%s.refreshid" % embyitem)
|
refresh_id = window("%s.refreshid" % embyitem)
|
||||||
playMethod = window("%s.playmethod" % embyitem)
|
playMethod = window("%s.playmethod" % embyitem)
|
||||||
itemType = window("%s.type" % embyitem)
|
itemType = window("%s.type" % embyitem)
|
||||||
|
try:
|
||||||
|
playcount = int(window("%s.playcount" % embyitem))
|
||||||
|
except ValueError:
|
||||||
|
playcount = 0
|
||||||
window('emby_skipWatched%s' % itemId, value="true")
|
window('emby_skipWatched%s' % itemId, value="true")
|
||||||
|
|
||||||
log("Playing itemtype is: %s" % itemType, 1)
|
self.logMsg("Playing itemtype is: %s" % itemType, 1)
|
||||||
|
|
||||||
customseek = window('emby_customPlaylist.seektime')
|
customseek = window('plex_customplaylist.seektime')
|
||||||
if (window('emby_customPlaylist') == "true" and customseek):
|
if customseek:
|
||||||
# Start at, when using custom playlist (play to Kodi from webclient)
|
# Start at, when using custom playlist (play to Kodi from webclient)
|
||||||
log("Seeking to: %s" % customseek, 1)
|
self.logMsg("Seeking to: %s" % customseek, 1)
|
||||||
xbmcplayer.seekTime(int(customseek))
|
try:
|
||||||
window('emby_customPlaylist.seektime', clear=True)
|
self.seekTime(int(customseek))
|
||||||
|
except:
|
||||||
|
self.logMsg('Could not seek!', -1)
|
||||||
|
window('plex_customplaylist.seektime', clear=True)
|
||||||
|
|
||||||
seekTime = xbmcplayer.getTime()
|
try:
|
||||||
|
seekTime = self.getTime()
|
||||||
|
except RuntimeError:
|
||||||
|
self.logMsg('Could not get current seektime from xbmc player', -1)
|
||||||
|
seekTime = 0
|
||||||
|
|
||||||
# Get playback volume
|
# Get playback volume
|
||||||
volume_query = {
|
volume_query = {
|
||||||
|
@ -193,7 +196,8 @@ class Player(xbmc.Player):
|
||||||
|
|
||||||
if mapping: # Set in playbackutils.py
|
if mapping: # Set in playbackutils.py
|
||||||
|
|
||||||
log("Mapping for external subtitles index: %s" % mapping, 2)
|
self.logMsg("Mapping for external subtitles index: %s"
|
||||||
|
% mapping, 2)
|
||||||
externalIndex = json.loads(mapping)
|
externalIndex = json.loads(mapping)
|
||||||
|
|
||||||
if externalIndex.get(str(indexSubs)):
|
if externalIndex.get(str(indexSubs)):
|
||||||
|
@ -218,18 +222,24 @@ class Player(xbmc.Player):
|
||||||
try:
|
try:
|
||||||
runtime = int(runtime)
|
runtime = int(runtime)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
runtime = xbmcplayer.getTotalTime()
|
try:
|
||||||
log("Runtime is missing, Kodi runtime: %s" % runtime, 1)
|
runtime = self.getTotalTime()
|
||||||
|
self.logMsg("Runtime is missing, Kodi runtime: %s"
|
||||||
|
% runtime, 1)
|
||||||
|
except:
|
||||||
|
self.logMsg('Could not get kodi runtime, setting to zero', -1)
|
||||||
|
runtime = 0
|
||||||
|
|
||||||
playQueueVersion = window('playQueueVersion')
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
playQueueID = window('playQueueID')
|
emby_dbitem = emby_db.getItem_byId(itemId)
|
||||||
playQueueItemID = window('plex_%s.playQueueItemID' % currentFile)
|
try:
|
||||||
|
fileid = emby_dbitem[1]
|
||||||
|
except TypeError:
|
||||||
|
self.logMsg("Could not find fileid in plex db.", 1)
|
||||||
|
fileid = None
|
||||||
# Save data map for updates and position calls
|
# Save data map for updates and position calls
|
||||||
data = {
|
data = {
|
||||||
'playQueueVersion': playQueueVersion,
|
'runtime': runtime,
|
||||||
'playQueueID': playQueueID,
|
|
||||||
'playQueueItemID': playQueueItemID,
|
|
||||||
'runtime': runtime * 1000,
|
|
||||||
'item_id': itemId,
|
'item_id': itemId,
|
||||||
'refresh_id': refresh_id,
|
'refresh_id': refresh_id,
|
||||||
'currentfile': currentFile,
|
'currentfile': currentFile,
|
||||||
|
@ -237,11 +247,14 @@ class Player(xbmc.Player):
|
||||||
'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
|
'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
|
||||||
'playmethod': playMethod,
|
'playmethod': playMethod,
|
||||||
'Type': itemType,
|
'Type': itemType,
|
||||||
'currentPosition': int(seekTime)
|
'currentPosition': int(seekTime),
|
||||||
|
'fileid': fileid,
|
||||||
|
'itemType': itemType,
|
||||||
|
'playcount': playcount
|
||||||
}
|
}
|
||||||
|
|
||||||
self.played_info[currentFile] = data
|
self.played_info[currentFile] = data
|
||||||
log("ADDING_FILE: %s" % self.played_info, 1)
|
self.logMsg("ADDING_FILE: %s" % data, 1)
|
||||||
|
|
||||||
# log some playback stats
|
# log some playback stats
|
||||||
'''if(itemType != None):
|
'''if(itemType != None):
|
||||||
|
@ -258,199 +271,48 @@ class Player(xbmc.Player):
|
||||||
else:
|
else:
|
||||||
self.playStats[playMethod] = 1'''
|
self.playStats[playMethod] = 1'''
|
||||||
|
|
||||||
def reportPlayback(self):
|
|
||||||
# Don't use if Plex Companion is enabled
|
|
||||||
if not self.doNotify:
|
|
||||||
return
|
|
||||||
|
|
||||||
log = self.logMsg
|
|
||||||
|
|
||||||
log("reportPlayback Called", 2)
|
|
||||||
|
|
||||||
# Get current file
|
|
||||||
currentFile = self.currentFile
|
|
||||||
data = self.played_info.get(currentFile)
|
|
||||||
|
|
||||||
# only report playback if emby has initiated the playback (item_id has value)
|
|
||||||
if data:
|
|
||||||
# Get playback inforation
|
|
||||||
itemId = data['item_id']
|
|
||||||
audioindex = data['AudioStreamIndex']
|
|
||||||
subtitleindex = data['SubtitleStreamIndex']
|
|
||||||
playTime = data['currentPosition']
|
|
||||||
playMethod = data['playmethod']
|
|
||||||
paused = data.get('paused', False)
|
|
||||||
duration = data.get('runtime', '')
|
|
||||||
|
|
||||||
|
|
||||||
# Get playback volume
|
|
||||||
volume_query = {
|
|
||||||
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": 1,
|
|
||||||
"method": "Application.GetProperties",
|
|
||||||
"params": {
|
|
||||||
|
|
||||||
"properties": ["volume", "muted"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = xbmc.executeJSONRPC(json.dumps(volume_query))
|
|
||||||
result = json.loads(result)
|
|
||||||
result = result.get('result')
|
|
||||||
|
|
||||||
volume = result.get('volume')
|
|
||||||
muted = result.get('muted')
|
|
||||||
|
|
||||||
# Postdata for the websocketclient report
|
|
||||||
# postdata = {
|
|
||||||
|
|
||||||
# 'QueueableMediaTypes': "Video",
|
|
||||||
# 'CanSeek': True,
|
|
||||||
# 'ItemId': itemId,
|
|
||||||
# 'MediaSourceId': itemId,
|
|
||||||
# 'PlayMethod': playMethod,
|
|
||||||
# 'PositionTicks': int(playTime * 10000000),
|
|
||||||
# 'IsPaused': paused,
|
|
||||||
# 'VolumeLevel': volume,
|
|
||||||
# 'IsMuted': muted
|
|
||||||
# }
|
|
||||||
if paused == 'stopped':
|
|
||||||
state = 'stopped'
|
|
||||||
elif paused is True:
|
|
||||||
state = 'paused'
|
|
||||||
else:
|
|
||||||
state = 'playing'
|
|
||||||
postdata = {
|
|
||||||
'ratingKey': itemId,
|
|
||||||
'state': state, # 'stopped', 'paused', 'buffering', 'playing'
|
|
||||||
'time': int(playTime) * 1000,
|
|
||||||
'duration': int(duration) * 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
# For PMS playQueues/playlists only
|
|
||||||
if data.get('playQueueID'):
|
|
||||||
postdata['containerKey'] = '/playQueues/' + data.get('playQueueID')
|
|
||||||
postdata['playQueueVersion'] = data.get('playQueueVersion')
|
|
||||||
postdata['playQueueItemID'] = data.get('playQueueItemID')
|
|
||||||
|
|
||||||
if playMethod == "Transcode":
|
|
||||||
# Track can't be changed, keep reporting the same index
|
|
||||||
postdata['AudioStreamIndex'] = audioindex
|
|
||||||
postdata['AudioStreamIndex'] = subtitleindex
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Get current audio and subtitles track
|
|
||||||
tracks_query = {
|
|
||||||
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": 1,
|
|
||||||
"method": "Player.GetProperties",
|
|
||||||
"params": {
|
|
||||||
|
|
||||||
"playerid": 1,
|
|
||||||
"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = xbmc.executeJSONRPC(json.dumps(tracks_query))
|
|
||||||
result = json.loads(result)
|
|
||||||
result = result.get('result')
|
|
||||||
|
|
||||||
try: # Audio tracks
|
|
||||||
indexAudio = result['currentaudiostream']['index']
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
indexAudio = 0
|
|
||||||
|
|
||||||
try: # Subtitles tracks
|
|
||||||
indexSubs = result['currentsubtitle']['index']
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
indexSubs = 0
|
|
||||||
|
|
||||||
try: # If subtitles are enabled
|
|
||||||
subsEnabled = result['subtitleenabled']
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
subsEnabled = ""
|
|
||||||
|
|
||||||
# Postdata for the audio
|
|
||||||
data['AudioStreamIndex'], postdata['AudioStreamIndex'] = [indexAudio + 1] * 2
|
|
||||||
|
|
||||||
# Postdata for the subtitles
|
|
||||||
if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
|
|
||||||
|
|
||||||
# Number of audiotracks to help get Emby Index
|
|
||||||
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
|
|
||||||
mapping = utils.window("emby_%s.indexMapping" % currentFile)
|
|
||||||
|
|
||||||
if mapping: # Set in PlaybackUtils.py
|
|
||||||
|
|
||||||
log("Mapping for external subtitles index: %s" % mapping, 2)
|
|
||||||
externalIndex = json.loads(mapping)
|
|
||||||
|
|
||||||
if externalIndex.get(str(indexSubs)):
|
|
||||||
# If the current subtitle is in the mapping
|
|
||||||
subindex = [externalIndex[str(indexSubs)]] * 2
|
|
||||||
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
|
|
||||||
else:
|
|
||||||
# Internal subtitle currently selected
|
|
||||||
subindex = [indexSubs - len(externalIndex) + audioTracks + 1] * 2
|
|
||||||
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
|
|
||||||
|
|
||||||
else: # Direct paths enabled scenario or no external subtitles set
|
|
||||||
subindex = [indexSubs + audioTracks + 1] * 2
|
|
||||||
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
|
|
||||||
else:
|
|
||||||
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [""] * 2
|
|
||||||
|
|
||||||
# Report progress via websocketclient
|
|
||||||
# postdata = json.dumps(postdata)
|
|
||||||
# self.ws.sendProgressUpdate(postdata)
|
|
||||||
self.doUtils(
|
|
||||||
"{server}/:/timeline?" + urlencode(postdata), type="GET")
|
|
||||||
|
|
||||||
def onPlayBackPaused(self):
|
def onPlayBackPaused(self):
|
||||||
|
|
||||||
currentFile = self.currentFile
|
currentFile = self.currentFile
|
||||||
self.logMsg("PLAYBACK_PAUSED: %s" % currentFile.decode('utf-8'), 2)
|
self.logMsg("PLAYBACK_PAUSED: %s" % utils.tryDecode(currentFile), 2)
|
||||||
|
|
||||||
if self.played_info.get(currentFile):
|
if self.played_info.get(currentFile):
|
||||||
self.played_info[currentFile]['paused'] = True
|
self.played_info[currentFile]['paused'] = True
|
||||||
|
|
||||||
self.reportPlayback()
|
|
||||||
|
|
||||||
def onPlayBackResumed(self):
|
def onPlayBackResumed(self):
|
||||||
|
|
||||||
currentFile = self.currentFile
|
currentFile = self.currentFile
|
||||||
self.logMsg("PLAYBACK_RESUMED: %s" % currentFile.decode('utf-8'), 2)
|
self.logMsg("PLAYBACK_RESUMED: %s" % utils.tryDecode(currentFile), 2)
|
||||||
|
|
||||||
if self.played_info.get(currentFile):
|
if self.played_info.get(currentFile):
|
||||||
self.played_info[currentFile]['paused'] = False
|
self.played_info[currentFile]['paused'] = False
|
||||||
|
|
||||||
self.reportPlayback()
|
|
||||||
|
|
||||||
def onPlayBackSeek(self, time, seekOffset):
|
def onPlayBackSeek(self, time, seekOffset):
|
||||||
# Make position when seeking a bit more accurate
|
# Make position when seeking a bit more accurate
|
||||||
currentFile = self.currentFile
|
currentFile = self.currentFile
|
||||||
self.logMsg("PLAYBACK_SEEK: %s" % currentFile.decode('utf-8'), 2)
|
self.logMsg("PLAYBACK_SEEK: %s" % utils.tryDecode(currentFile), 2)
|
||||||
|
|
||||||
if self.played_info.get(currentFile):
|
if self.played_info.get(currentFile):
|
||||||
position = self.xbmcplayer.getTime()
|
try:
|
||||||
|
position = self.getTime()
|
||||||
|
except RuntimeError:
|
||||||
|
# When Kodi is not playing
|
||||||
|
return
|
||||||
self.played_info[currentFile]['currentPosition'] = position
|
self.played_info[currentFile]['currentPosition'] = position
|
||||||
|
|
||||||
self.reportPlayback()
|
|
||||||
|
|
||||||
def onPlayBackStopped(self):
|
def onPlayBackStopped(self):
|
||||||
# Will be called when user stops xbmc playing a file
|
# Will be called when user stops xbmc playing a file
|
||||||
|
|
||||||
log = self.logMsg
|
|
||||||
window = utils.window
|
window = utils.window
|
||||||
log("ONPLAYBACK_STOPPED", 1)
|
self.logMsg("ONPLAYBACK_STOPPED", 1)
|
||||||
|
|
||||||
self.stopAll()
|
self.stopAll()
|
||||||
|
|
||||||
window('Plex_currently_playing_itemid', clear=True)
|
window('Plex_currently_playing_itemid', clear=True)
|
||||||
window('emby_customPlaylist', clear=True)
|
window('plex_customplaylist', clear=True)
|
||||||
window('emby_customPlaylist.seektime', clear=True)
|
window('plex_customplaylist.seektime', clear=True)
|
||||||
window('emby_customPlaylist.seektime', clear=True)
|
window('plex_customplaylist.seektime', clear=True)
|
||||||
log("Clear playlist properties.", 1)
|
self.logMsg("Clear playlist properties.", 1)
|
||||||
|
|
||||||
def onPlayBackEnded(self):
|
def onPlayBackEnded(self):
|
||||||
# Will be called when xbmc stops playing a file, because the file ended
|
# Will be called when xbmc stops playing a file, because the file ended
|
||||||
|
@ -459,31 +321,28 @@ class Player(xbmc.Player):
|
||||||
|
|
||||||
def stopAll(self):
|
def stopAll(self):
|
||||||
|
|
||||||
log = self.logMsg
|
|
||||||
lang = utils.language
|
lang = utils.language
|
||||||
settings = utils.settings
|
settings = utils.settings
|
||||||
|
|
||||||
doUtils = self.doUtils
|
|
||||||
|
|
||||||
if not self.played_info:
|
if not self.played_info:
|
||||||
return
|
return
|
||||||
|
|
||||||
log("Played_information: %s" % self.played_info, 1)
|
self.logMsg("Played_information: %s" % self.played_info, 1)
|
||||||
# Process each items
|
# Process each items
|
||||||
for item in self.played_info:
|
for item in self.played_info:
|
||||||
|
|
||||||
data = self.played_info.get(item)
|
data = self.played_info.get(item)
|
||||||
if data:
|
if data:
|
||||||
|
|
||||||
log("Item path: %s" % item, 2)
|
self.logMsg("Item path: %s" % item, 2)
|
||||||
log("Item data: %s" % data, 2)
|
self.logMsg("Item data: %s" % data, 2)
|
||||||
|
|
||||||
runtime = data['runtime']
|
runtime = data['runtime']
|
||||||
currentPosition = data['currentPosition']
|
currentPosition = data['currentPosition']
|
||||||
itemid = data['item_id']
|
itemid = data['item_id']
|
||||||
refresh_id = data['refresh_id']
|
refresh_id = data['refresh_id']
|
||||||
currentFile = data['currentfile']
|
currentFile = data['currentfile']
|
||||||
type = data['Type']
|
media_type = data['Type']
|
||||||
playMethod = data['playmethod']
|
playMethod = data['playmethod']
|
||||||
|
|
||||||
# Prevent manually mark as watched in Kodi monitor
|
# Prevent manually mark as watched in Kodi monitor
|
||||||
|
@ -491,21 +350,31 @@ class Player(xbmc.Player):
|
||||||
|
|
||||||
if currentPosition and runtime:
|
if currentPosition and runtime:
|
||||||
try:
|
try:
|
||||||
percentComplete = currentPosition / int(runtime)
|
percentComplete = float(currentPosition) / float(runtime)
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
# Runtime is 0.
|
# Runtime is 0.
|
||||||
percentComplete = 0
|
percentComplete = 0
|
||||||
|
|
||||||
markPlayedAt = float(settings('markPlayed')) / 100
|
|
||||||
log("Percent complete: %s Mark played at: %s"
|
|
||||||
% (percentComplete, markPlayedAt), 1)
|
|
||||||
|
|
||||||
|
markPlayed = 0.90
|
||||||
|
self.logMsg("Percent complete: %s Mark played at: %s"
|
||||||
|
% (percentComplete, markPlayed), 1)
|
||||||
|
if percentComplete >= markPlayed:
|
||||||
|
# Tell Kodi that we've finished watching (Plex knows)
|
||||||
|
if (data['fileid'] is not None and
|
||||||
|
data['itemType'] in ('movie', 'episode')):
|
||||||
|
with kodidb.GetKodiDB('video') as kodi_db:
|
||||||
|
kodi_db.addPlaystate(
|
||||||
|
data['fileid'],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
data['playcount'] + 1,
|
||||||
|
utils.DateToKodi(utils.getUnixTimestamp()))
|
||||||
# Send the delete action to the server.
|
# Send the delete action to the server.
|
||||||
offerDelete = False
|
offerDelete = False
|
||||||
|
|
||||||
if type == "Episode" and settings('deleteTV') == "true":
|
if media_type == "Episode" and settings('deleteTV') == "true":
|
||||||
offerDelete = True
|
offerDelete = True
|
||||||
elif type == "Movie" and settings('deleteMovies') == "true":
|
elif media_type == "Movie" and settings('deleteMovies') == "true":
|
||||||
offerDelete = True
|
offerDelete = True
|
||||||
|
|
||||||
if settings('offerDelete') != "true":
|
if settings('offerDelete') != "true":
|
||||||
|
@ -514,19 +383,18 @@ class Player(xbmc.Player):
|
||||||
|
|
||||||
# Plex: never delete
|
# Plex: never delete
|
||||||
offerDelete = False
|
offerDelete = False
|
||||||
if percentComplete >= markPlayedAt and offerDelete:
|
if percentComplete >= markPlayed and offerDelete:
|
||||||
resp = xbmcgui.Dialog().yesno(
|
resp = xbmcgui.Dialog().yesno(
|
||||||
lang(30091),
|
lang(30091),
|
||||||
lang(33015),
|
lang(33015),
|
||||||
autoclose=120000)
|
autoclose=120000)
|
||||||
if not resp:
|
if not resp:
|
||||||
log("User skipped deletion.", 1)
|
self.logMsg("User skipped deletion.", 1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
url = "{server}/emby/Items/%s?format=json" % itemid
|
url = "{server}/emby/Items/%s?format=json" % itemid
|
||||||
log("Deleting request: %s" % itemid, 1)
|
self.logMsg("Deleting request: %s" % itemid, 1)
|
||||||
doUtils(url, type="DELETE")
|
self.doUtils(url, action_type="DELETE")
|
||||||
self.stopPlayback(data)
|
|
||||||
|
|
||||||
# Clean the WINDOW properties
|
# Clean the WINDOW properties
|
||||||
for filename in self.played_info:
|
for filename in self.played_info:
|
||||||
|
@ -536,35 +404,18 @@ class Player(xbmc.Player):
|
||||||
'emby_%s.refreshid' % filename,
|
'emby_%s.refreshid' % filename,
|
||||||
'emby_%s.playmethod' % filename,
|
'emby_%s.playmethod' % filename,
|
||||||
'emby_%s.type' % filename,
|
'emby_%s.type' % filename,
|
||||||
'plex_%s.playQueueItemID' % filename,
|
'emby_%s.runtime' % filename,
|
||||||
'plex_%s.playlistPosition' % filename,
|
'emby_%s.playcount' % filename,
|
||||||
'plex_%s.guid' % filename
|
'plex_%s.playlistPosition' % filename
|
||||||
)
|
)
|
||||||
for item in cleanup:
|
for item in cleanup:
|
||||||
utils.window(item, clear=True)
|
utils.window(item, clear=True)
|
||||||
|
|
||||||
# Stop transcoding
|
# Stop transcoding
|
||||||
if playMethod == "Transcode":
|
if playMethod == "Transcode":
|
||||||
log("Transcoding for %s terminating" % itemid, 1)
|
self.logMsg("Transcoding for %s terminating" % itemid, 1)
|
||||||
doUtils(
|
self.doUtils(
|
||||||
"{server}/video/:/transcode/universal/stop",
|
"{server}/video/:/transcode/universal/stop",
|
||||||
parameters={'session': self.clientInfo.getDeviceId()})
|
parameters={'session': self.clientInfo.getDeviceId()})
|
||||||
|
|
||||||
self.played_info.clear()
|
self.played_info.clear()
|
||||||
|
|
||||||
def stopPlayback(self, data):
|
|
||||||
self.logMsg("stopPlayback called", 1)
|
|
||||||
|
|
||||||
itemId = data['item_id']
|
|
||||||
playTime = data['currentPosition']
|
|
||||||
duration = data.get('runtime', '')
|
|
||||||
|
|
||||||
url = "{server}/:/timeline?"
|
|
||||||
args = {
|
|
||||||
'ratingKey': itemId,
|
|
||||||
'state': 'stopped', # 'stopped', 'paused', 'buffering', 'playing'
|
|
||||||
'time': int(playTime) * 1000,
|
|
||||||
'duration': int(duration)
|
|
||||||
}
|
|
||||||
url = url + urlencode(args)
|
|
||||||
self.doUtils(url, type="GET")
|
|
||||||
|
|
|
@ -4,144 +4,287 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
|
from threading import Lock
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcgui
|
|
||||||
|
|
||||||
import embydb_functions as embydb
|
import embydb_functions as embydb
|
||||||
import read_embyserver as embyserver
|
|
||||||
import utils
|
import utils
|
||||||
|
import playbackutils
|
||||||
import PlexFunctions
|
import PlexFunctions
|
||||||
import PlexAPI
|
import PlexAPI
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class lockMethod:
|
||||||
|
"""
|
||||||
|
Decorator for class methods to lock hem completely. Same lock is used for
|
||||||
|
every single decorator and instance used!
|
||||||
|
|
||||||
|
Here only used for Playlist()
|
||||||
|
"""
|
||||||
|
lock = Lock()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def decorate(cls, func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
with cls.lock:
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
return result
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
@utils.logging
|
@utils.logging
|
||||||
class Playlist():
|
class Playlist():
|
||||||
|
"""
|
||||||
|
Initiate with Playlist(typus='video' or 'music')
|
||||||
|
"""
|
||||||
|
# Borg - multiple instances, shared state
|
||||||
|
_shared_state = {}
|
||||||
|
|
||||||
|
typus = None
|
||||||
|
queueId = None
|
||||||
|
playQueueVersion = None
|
||||||
|
guid = None
|
||||||
|
playlistId = None
|
||||||
|
player = xbmc.Player()
|
||||||
|
# "interal" PKC playlist
|
||||||
|
items = []
|
||||||
|
|
||||||
|
@lockMethod.decorate
|
||||||
|
def __init__(self, typus=None):
|
||||||
|
# Borg
|
||||||
|
self.__dict__ = self._shared_state
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.userid = utils.window('currUserId')
|
self.userid = utils.window('currUserId')
|
||||||
self.server = utils.window('pms_server')
|
self.server = utils.window('pms_server')
|
||||||
|
# Construct the Kodi playlist instance
|
||||||
|
if self.typus == typus:
|
||||||
|
return
|
||||||
|
if typus == 'video':
|
||||||
|
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||||
|
self.typus = 'video'
|
||||||
|
self.logMsg('Initiated video playlist', 1)
|
||||||
|
elif typus == 'music':
|
||||||
|
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
||||||
|
self.typus = 'music'
|
||||||
|
self.logMsg('Initiated music playlist', 1)
|
||||||
|
else:
|
||||||
|
self.playlist = None
|
||||||
|
self.typus = None
|
||||||
|
self.logMsg('Empty playlist initiated', 1)
|
||||||
|
if self.playlist is not None:
|
||||||
|
self.playlistId = self.playlist.getPlayListId()
|
||||||
|
|
||||||
self.emby = embyserver.Read_EmbyServer()
|
@lockMethod.decorate
|
||||||
|
def getQueueIdFromPosition(self, playlistPosition):
|
||||||
|
return self.items[playlistPosition]['playQueueItemID']
|
||||||
|
|
||||||
def playAll(self, itemids, startat):
|
@lockMethod.decorate
|
||||||
log = self.logMsg
|
def Typus(self, value=None):
|
||||||
window = utils.window
|
if value:
|
||||||
|
self.typus = value
|
||||||
|
else:
|
||||||
|
return self.typus
|
||||||
|
|
||||||
embyconn = utils.kodiSQL('emby')
|
@lockMethod.decorate
|
||||||
embycursor = embyconn.cursor()
|
def PlayQueueVersion(self, value=None):
|
||||||
emby_db = embydb.Embydb_Functions(embycursor)
|
if value:
|
||||||
|
self.playQueueVersion = value
|
||||||
|
else:
|
||||||
|
return self.playQueueVersion
|
||||||
|
|
||||||
player = xbmc.Player()
|
@lockMethod.decorate
|
||||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
def QueueId(self, value=None):
|
||||||
playlist.clear()
|
if value:
|
||||||
|
self.queueId = value
|
||||||
|
else:
|
||||||
|
return self.queueId
|
||||||
|
|
||||||
log("---*** PLAY ALL ***---", 1)
|
@lockMethod.decorate
|
||||||
log("Items: %s and start at: %s" % (itemids, startat), 1)
|
def Guid(self, value=None):
|
||||||
|
if value:
|
||||||
|
self.guid = value
|
||||||
|
else:
|
||||||
|
return self.guid
|
||||||
|
|
||||||
started = False
|
@lockMethod.decorate
|
||||||
window('emby_customplaylist', value="true")
|
def clear(self):
|
||||||
|
"""
|
||||||
if startat != 0:
|
Empties current Kodi playlist and associated variables
|
||||||
# Seek to the starting position
|
"""
|
||||||
window('emby_customplaylist.seektime', str(startat))
|
self.logMsg('Clearing playlist', 1)
|
||||||
|
self.playlist.clear()
|
||||||
|
self.items = []
|
||||||
|
self.queueId = None
|
||||||
|
self.playQueueVersion = None
|
||||||
|
self.guid = None
|
||||||
|
|
||||||
|
def _initiatePlaylist(self):
|
||||||
|
self.logMsg('Initiating playlist', 1)
|
||||||
|
playlist = None
|
||||||
with embydb.GetEmbyDB() as emby_db:
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
for itemid in itemids:
|
for item in self.items:
|
||||||
|
itemid = item['plexId']
|
||||||
embydb_item = emby_db.getItem_byId(itemid)
|
embydb_item = emby_db.getItem_byId(itemid)
|
||||||
try:
|
try:
|
||||||
dbid = embydb_item[0]
|
|
||||||
mediatype = embydb_item[4]
|
mediatype = embydb_item[4]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# Item is not found in our database, add item manually
|
self.logMsg('Couldnt find item %s in Kodi db'
|
||||||
log("Item was not found in the database, manually adding item.", 1)
|
% itemid, 1)
|
||||||
item = PlexFunctions.GetPlexMetadata(itemid)
|
item = PlexFunctions.GetPlexMetadata(itemid)
|
||||||
if item is None or item == 401:
|
if item in (None, 401):
|
||||||
log('Could not download itemid %s' % itemid, -1)
|
self.logMsg('Couldnt find item %s on PMS, trying next'
|
||||||
|
% itemid, 1)
|
||||||
|
continue
|
||||||
|
if PlexAPI.API(item[0]).getType() == 'track':
|
||||||
|
playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
||||||
|
self.logMsg('Music playlist initiated', 1)
|
||||||
|
self.typus = 'music'
|
||||||
else:
|
else:
|
||||||
self.addtoPlaylist_xbmc(playlist, item)
|
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||||
|
self.logMsg('Video playlist initiated', 1)
|
||||||
|
self.typus = 'video'
|
||||||
|
else:
|
||||||
|
if mediatype == 'song':
|
||||||
|
playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
||||||
|
self.logMsg('Music playlist initiated', 1)
|
||||||
|
self.typus = 'music'
|
||||||
|
else:
|
||||||
|
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||||
|
self.logMsg('Video playlist initiated', 1)
|
||||||
|
self.typus = 'video'
|
||||||
|
break
|
||||||
|
self.playlist = playlist
|
||||||
|
if self.playlist is not None:
|
||||||
|
self.playlistId = self.playlist.getPlayListId()
|
||||||
|
|
||||||
|
def _processItems(self, startitem, startPlayer=False):
|
||||||
|
startpos = None
|
||||||
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
|
for pos, item in enumerate(self.items):
|
||||||
|
kodiId = None
|
||||||
|
plexId = item['plexId']
|
||||||
|
embydb_item = emby_db.getItem_byId(plexId)
|
||||||
|
try:
|
||||||
|
kodiId = embydb_item[0]
|
||||||
|
mediatype = embydb_item[4]
|
||||||
|
except TypeError:
|
||||||
|
self.logMsg('Couldnt find item %s in Kodi db' % plexId, 1)
|
||||||
|
xml = PlexFunctions.GetPlexMetadata(plexId)
|
||||||
|
if xml in (None, 401):
|
||||||
|
self.logMsg('Could not download plexId %s'
|
||||||
|
% plexId, -1)
|
||||||
|
else:
|
||||||
|
self.logMsg('Downloaded xml metadata, adding now', 1)
|
||||||
|
self._addtoPlaylist_xbmc(xml[0])
|
||||||
else:
|
else:
|
||||||
# Add to playlist
|
# Add to playlist
|
||||||
self.addtoPlaylist(dbid, mediatype)
|
self.logMsg("Adding %s PlexId %s, KodiId %s to playlist."
|
||||||
|
% (mediatype, plexId, kodiId), 1)
|
||||||
|
self._addtoPlaylist(kodiId, mediatype)
|
||||||
|
# Add the kodiId
|
||||||
|
if kodiId is not None:
|
||||||
|
item['kodiId'] = str(kodiId)
|
||||||
|
if (startpos is None and startitem[1] == item[startitem[0]]):
|
||||||
|
startpos = pos
|
||||||
|
|
||||||
log("Adding %s to playlist." % itemid, 1)
|
if startPlayer is True and len(self.playlist) > 0:
|
||||||
|
if startpos is not None:
|
||||||
if not started:
|
self.player.play(self.playlist, startpos=startpos)
|
||||||
started = True
|
|
||||||
player.play(playlist)
|
|
||||||
|
|
||||||
self.verifyPlaylist()
|
|
||||||
|
|
||||||
def modifyPlaylist(self, itemids):
|
|
||||||
|
|
||||||
log = self.logMsg
|
|
||||||
|
|
||||||
embyconn = utils.kodiSQL('emby')
|
|
||||||
embycursor = embyconn.cursor()
|
|
||||||
emby_db = embydb.Embydb_Functions(embycursor)
|
|
||||||
|
|
||||||
log("---*** ADD TO PLAYLIST ***---", 1)
|
|
||||||
log("Items: %s" % itemids, 1)
|
|
||||||
|
|
||||||
# player = xbmc.Player()
|
|
||||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
|
||||||
|
|
||||||
for itemid in itemids:
|
|
||||||
embydb_item = emby_db.getItem_byId(itemid)
|
|
||||||
try:
|
|
||||||
dbid = embydb_item[0]
|
|
||||||
mediatype = embydb_item[4]
|
|
||||||
except TypeError:
|
|
||||||
# Item is not found in our database, add item manually
|
|
||||||
item = self.emby.getItem(itemid)
|
|
||||||
self.addtoPlaylist_xbmc(playlist, item)
|
|
||||||
else:
|
else:
|
||||||
# Add to playlist
|
self.logMsg('Never received a starting item for playlist, '
|
||||||
self.addtoPlaylist(dbid, mediatype)
|
'starting with the first entry', 1)
|
||||||
|
self.player.play(self.playlist)
|
||||||
|
|
||||||
log("Adding %s to playlist." % itemid, 1)
|
@lockMethod.decorate
|
||||||
|
def playAll(self, items, startitem, offset):
|
||||||
|
"""
|
||||||
|
items: list of dicts of the form
|
||||||
|
{
|
||||||
|
'playQueueItemID': Plex playQueueItemID, e.g. '29175'
|
||||||
|
'plexId': Plex ratingKey, e.g. '125'
|
||||||
|
'kodiId': Kodi's db id of the same item
|
||||||
|
}
|
||||||
|
|
||||||
self.verifyPlaylist()
|
startitem: tuple (typus, id), where typus is either
|
||||||
embycursor.close()
|
'playQueueItemID' or 'plexId' and id is the corresponding
|
||||||
return playlist
|
id as a string
|
||||||
|
offset: First item's time offset to play in Kodi time (an int)
|
||||||
|
"""
|
||||||
|
self.logMsg("---*** PLAY ALL ***---", 1)
|
||||||
|
self.logMsg('Startitem: %s, offset: %s, items: %s'
|
||||||
|
% (startitem, offset, items), 1)
|
||||||
|
self.items = items
|
||||||
|
if self.playlist is None:
|
||||||
|
self._initiatePlaylist()
|
||||||
|
if self.playlist is None:
|
||||||
|
self.logMsg('Could not create playlist, abort', -1)
|
||||||
|
return
|
||||||
|
|
||||||
|
utils.window('plex_customplaylist', value="true")
|
||||||
|
if offset != 0:
|
||||||
|
# Seek to the starting position
|
||||||
|
utils.window('plex_customplaylist.seektime', str(offset))
|
||||||
|
self._processItems(startitem, startPlayer=True)
|
||||||
|
# Log playlist
|
||||||
|
self._verifyPlaylist()
|
||||||
|
self.logMsg('Internal playlist: %s' % self.items, 2)
|
||||||
|
|
||||||
|
@lockMethod.decorate
|
||||||
|
def modifyPlaylist(self, itemids):
|
||||||
|
self.logMsg("---*** ADD TO PLAYLIST ***---", 1)
|
||||||
|
self.logMsg("Items: %s" % itemids, 1)
|
||||||
|
|
||||||
|
self._initiatePlaylist(itemids)
|
||||||
|
self._processItems(itemids, startPlayer=True)
|
||||||
|
|
||||||
|
self._verifyPlaylist()
|
||||||
|
|
||||||
|
@lockMethod.decorate
|
||||||
def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
|
def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
|
||||||
|
"""
|
||||||
|
mediatype: Kodi type: 'movie', 'episode', 'musicvideo', 'artist',
|
||||||
|
'album', 'song', 'genre'
|
||||||
|
"""
|
||||||
|
self._addtoPlaylist(dbid=None, mediatype=None, url=None)
|
||||||
|
|
||||||
|
def _addtoPlaylist(self, dbid=None, mediatype=None, url=None):
|
||||||
pl = {
|
pl = {
|
||||||
|
|
||||||
'jsonrpc': "2.0",
|
'jsonrpc': "2.0",
|
||||||
'id': 1,
|
'id': 1,
|
||||||
'method': "Playlist.Add",
|
'method': "Playlist.Add",
|
||||||
'params': {
|
'params': {
|
||||||
|
'playlistid': self.playlistId
|
||||||
'playlistid': 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if dbid is not None:
|
if dbid is not None:
|
||||||
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
|
pl['params']['item'] = {'%sid' % utils.tryEncode(mediatype):
|
||||||
|
int(dbid)}
|
||||||
else:
|
else:
|
||||||
pl['params']['item'] = {'file': url}
|
pl['params']['item'] = {'file': url}
|
||||||
|
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
|
||||||
|
|
||||||
result = xbmc.executeJSONRPC(json.dumps(pl))
|
def _addtoPlaylist_xbmc(self, item):
|
||||||
self.logMsg(result, 2)
|
API = PlexAPI.API(item)
|
||||||
|
|
||||||
def addtoPlaylist_xbmc(self, playlist, item):
|
|
||||||
path = "plugin://plugin.video.plexkodiconnect.movies/"
|
|
||||||
params = {
|
params = {
|
||||||
'mode': "play",
|
'mode': "play",
|
||||||
'dbid': 999999999
|
'dbid': 999999999,
|
||||||
|
'id': API.getRatingKey(),
|
||||||
|
'filename': API.getKey()
|
||||||
}
|
}
|
||||||
API = PlexAPI.API(item[0])
|
playurl = "plugin://plugin.video.plexkodiconnect.movies/?%s" \
|
||||||
params['id'] = API.getRatingKey()
|
% urlencode(params)
|
||||||
params['filename'] = API.getKey()
|
|
||||||
playurl = path + '?' + urlencode(params)
|
|
||||||
|
|
||||||
listitem = xbmcgui.ListItem()
|
listitem = API.CreateListItemFromPlexItem()
|
||||||
|
playbackutils.PlaybackUtils(item).setArtwork(listitem)
|
||||||
|
|
||||||
playlist.add(playurl, listitem)
|
self.playlist.add(playurl, listitem)
|
||||||
|
|
||||||
|
@lockMethod.decorate
|
||||||
def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None):
|
def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None):
|
||||||
|
|
||||||
pl = {
|
pl = {
|
||||||
|
@ -151,20 +294,23 @@ class Playlist():
|
||||||
'method': "Playlist.Insert",
|
'method': "Playlist.Insert",
|
||||||
'params': {
|
'params': {
|
||||||
|
|
||||||
'playlistid': 1,
|
'playlistid': self.playlistId,
|
||||||
'position': position
|
'position': position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if dbid is not None:
|
if dbid is not None:
|
||||||
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
|
pl['params']['item'] = {'%sid' % utils.tryEncode(mediatype):
|
||||||
|
int(dbid)}
|
||||||
else:
|
else:
|
||||||
pl['params']['item'] = {'file': url}
|
pl['params']['item'] = {'file': url}
|
||||||
|
|
||||||
result = xbmc.executeJSONRPC(json.dumps(pl))
|
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
|
||||||
self.logMsg(result, 2)
|
|
||||||
|
|
||||||
|
@lockMethod.decorate
|
||||||
def verifyPlaylist(self):
|
def verifyPlaylist(self):
|
||||||
|
self._verifyPlaylist()
|
||||||
|
|
||||||
|
def _verifyPlaylist(self):
|
||||||
pl = {
|
pl = {
|
||||||
|
|
||||||
'jsonrpc': "2.0",
|
'jsonrpc': "2.0",
|
||||||
|
@ -172,13 +318,13 @@ class Playlist():
|
||||||
'method': "Playlist.GetItems",
|
'method': "Playlist.GetItems",
|
||||||
'params': {
|
'params': {
|
||||||
|
|
||||||
'playlistid': 1,
|
'playlistid': self.playlistId,
|
||||||
'properties': ['title', 'file']
|
'properties': ['title', 'file']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = xbmc.executeJSONRPC(json.dumps(pl))
|
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
|
||||||
self.logMsg(result, 2)
|
|
||||||
|
|
||||||
|
@lockMethod.decorate
|
||||||
def removefromPlaylist(self, position):
|
def removefromPlaylist(self, position):
|
||||||
|
|
||||||
pl = {
|
pl = {
|
||||||
|
@ -188,9 +334,8 @@ class Playlist():
|
||||||
'method': "Playlist.Remove",
|
'method': "Playlist.Remove",
|
||||||
'params': {
|
'params': {
|
||||||
|
|
||||||
'playlistid': 1,
|
'playlistid': self.playlistId,
|
||||||
'position': position
|
'position': position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = xbmc.executeJSONRPC(json.dumps(pl))
|
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
|
||||||
self.logMsg(result, 2)
|
|
||||||
|
|
|
@ -37,100 +37,82 @@ class PlayUtils():
|
||||||
|
|
||||||
playurl is utf-8 encoded!
|
playurl is utf-8 encoded!
|
||||||
"""
|
"""
|
||||||
log = self.logMsg
|
|
||||||
window = utils.window
|
|
||||||
|
|
||||||
self.API.setPartNumber(partNumber)
|
self.API.setPartNumber(partNumber)
|
||||||
playurl = self.isDirectPlay()
|
playurl = self.isDirectPlay()
|
||||||
|
|
||||||
if playurl:
|
if playurl is not None:
|
||||||
log("File is direct playing.", 1)
|
self.logMsg("File is direct playing.", 1)
|
||||||
playurl = playurl.encode('utf-8')
|
playurl = utils.tryEncode(playurl)
|
||||||
# Set playmethod property
|
# Set playmethod property
|
||||||
window('emby_%s.playmethod' % playurl, "DirectPlay")
|
utils.window('emby_%s.playmethod' % playurl, "DirectPlay")
|
||||||
|
|
||||||
elif self.isDirectStream():
|
elif self.isDirectStream():
|
||||||
self.logMsg("File is direct streaming.", 1)
|
self.logMsg("File is direct streaming.", 1)
|
||||||
playurl = self.API.getTranscodeVideoPath('DirectStream').encode('utf-8')
|
playurl = utils.tryEncode(
|
||||||
|
self.API.getTranscodeVideoPath('DirectStream'))
|
||||||
# Set playmethod property
|
# Set playmethod property
|
||||||
utils.window('emby_%s.playmethod' % playurl, "DirectStream")
|
utils.window('emby_%s.playmethod' % playurl, "DirectStream")
|
||||||
|
|
||||||
elif self.isTranscoding():
|
else:
|
||||||
log("File is transcoding.", 1)
|
self.logMsg("File is transcoding.", 1)
|
||||||
quality = {
|
playurl = utils.tryEncode(self.API.getTranscodeVideoPath(
|
||||||
'maxVideoBitrate': self.getBitrate(),
|
|
||||||
'videoResolution': self.getResolution(),
|
|
||||||
'videoQuality': '100'
|
|
||||||
}
|
|
||||||
playurl = self.API.getTranscodeVideoPath(
|
|
||||||
'Transcode',
|
'Transcode',
|
||||||
quality=quality).encode('utf-8')
|
quality={
|
||||||
|
'maxVideoBitrate': self.getBitrate(),
|
||||||
|
'videoResolution': self.getResolution(),
|
||||||
|
'videoQuality': '100'
|
||||||
|
}))
|
||||||
# Set playmethod property
|
# Set playmethod property
|
||||||
window('emby_%s.playmethod' % playurl, value="Transcode")
|
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||||
|
|
||||||
log("The playurl is: %s" % playurl, 1)
|
self.logMsg("The playurl is: %s" % playurl, 1)
|
||||||
return playurl
|
return playurl
|
||||||
|
|
||||||
def httpPlay(self):
|
def httpPlay(self):
|
||||||
# Audio, Video, Photo
|
# Audio, Video, Photo
|
||||||
item = self.item
|
|
||||||
server = self.server
|
|
||||||
|
|
||||||
itemid = item['Id']
|
itemid = self.item['Id']
|
||||||
mediatype = item['MediaType']
|
mediatype = self.item['MediaType']
|
||||||
|
|
||||||
if mediatype == "Audio":
|
if mediatype == "Audio":
|
||||||
playurl = "%s/emby/Audio/%s/stream" % (server, itemid)
|
playurl = "%s/emby/Audio/%s/stream" % (self.server, itemid)
|
||||||
else:
|
else:
|
||||||
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
|
playurl = "%s/emby/Videos/%s/stream?static=true" % (self.server, itemid)
|
||||||
|
|
||||||
return playurl
|
return playurl
|
||||||
|
|
||||||
def isDirectPlay(self):
|
def isDirectPlay(self):
|
||||||
"""
|
"""
|
||||||
Returns the path/playurl if successful, False otherwise
|
Returns the path/playurl if we can direct play, None otherwise
|
||||||
"""
|
"""
|
||||||
# True for e.g. plex.tv watch later
|
# True for e.g. plex.tv watch later
|
||||||
if self.API.shouldStream() is True:
|
if self.API.shouldStream() is True:
|
||||||
self.logMsg("Plex item optimized for direct streaming", 1)
|
self.logMsg("Plex item optimized for direct streaming", 1)
|
||||||
return False
|
return
|
||||||
|
|
||||||
# set to either 'Direct Stream=1' or 'Transcode=2'
|
# set to either 'Direct Stream=1' or 'Transcode=2'
|
||||||
|
# and NOT to 'Direct Play=0'
|
||||||
if utils.settings('playType') != "0":
|
if utils.settings('playType') != "0":
|
||||||
# User forcing to play via HTTP
|
# User forcing to play via HTTP
|
||||||
self.logMsg("User chose to not direct play", 1)
|
self.logMsg("User chose to not direct play", 1)
|
||||||
return False
|
return
|
||||||
|
if self.mustTranscode():
|
||||||
if self.h265enabled():
|
return
|
||||||
return False
|
return self.API.validatePlayurl(self.API.getFilePath(),
|
||||||
|
|
||||||
path = self.API.validatePlayurl(self.API.getFilePath(),
|
|
||||||
self.API.getType(),
|
self.API.getType(),
|
||||||
forceCheck=True)
|
forceCheck=True)
|
||||||
if path is None:
|
|
||||||
self.logMsg('Kodi cannot access file %s - no direct play'
|
|
||||||
% path, 1)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
self.logMsg('Kodi can access file %s - direct playing' % path, 1)
|
|
||||||
return path
|
|
||||||
|
|
||||||
def directPlay(self):
|
def directPlay(self):
|
||||||
|
|
||||||
item = self.item
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
playurl = item['MediaSources'][0]['Path']
|
playurl = self.item['MediaSources'][0]['Path']
|
||||||
except (IndexError, KeyError):
|
except (IndexError, KeyError):
|
||||||
playurl = item['Path']
|
playurl = self.item['Path']
|
||||||
|
|
||||||
if item.get('VideoType'):
|
if self.item.get('VideoType'):
|
||||||
# Specific format modification
|
# Specific format modification
|
||||||
type = item['VideoType']
|
if self.item['VideoType'] == "Dvd":
|
||||||
|
|
||||||
if type == "Dvd":
|
|
||||||
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
|
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
|
||||||
elif type == "BluRay":
|
elif self.item['VideoType'] == "BluRay":
|
||||||
playurl = "%s/BDMV/index.bdmv" % playurl
|
playurl = "%s/BDMV/index.bdmv" % playurl
|
||||||
|
|
||||||
# Assign network protocol
|
# Assign network protocol
|
||||||
|
@ -146,59 +128,71 @@ class PlayUtils():
|
||||||
|
|
||||||
def fileExists(self):
|
def fileExists(self):
|
||||||
|
|
||||||
log = self.logMsg
|
|
||||||
|
|
||||||
if 'Path' not in self.item:
|
if 'Path' not in self.item:
|
||||||
# File has no path defined in server
|
# File has no path defined in server
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Convert path to direct play
|
# Convert path to direct play
|
||||||
path = self.directPlay()
|
path = self.directPlay()
|
||||||
log("Verifying path: %s" % path, 1)
|
self.logMsg("Verifying path: %s" % path, 1)
|
||||||
|
|
||||||
if xbmcvfs.exists(path):
|
if xbmcvfs.exists(path):
|
||||||
log("Path exists.", 1)
|
self.logMsg("Path exists.", 1)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
elif ":" not in path:
|
elif ":" not in path:
|
||||||
log("Can't verify path, assumed linux. Still try to direct play.", 1)
|
self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
log("Failed to find file.", 1)
|
self.logMsg("Failed to find file.", 1)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def h265enabled(self):
|
def mustTranscode(self):
|
||||||
"""
|
"""
|
||||||
Returns True if we need to transcode
|
Returns True if we need to transcode because
|
||||||
|
- codec is in h265
|
||||||
|
- 10bit video codec
|
||||||
|
- HEVC codec
|
||||||
|
if the corresponding file settings are set to 'true'
|
||||||
"""
|
"""
|
||||||
videoCodec = self.API.getVideoCodec()
|
videoCodec = self.API.getVideoCodec()
|
||||||
self.logMsg("videoCodec: %s" % videoCodec, 2)
|
self.logMsg("videoCodec: %s" % videoCodec, 2)
|
||||||
codec = videoCodec['videocodec']
|
if (utils.settings('transcodeHi10P') == 'true' and
|
||||||
resolution = videoCodec['resolution']
|
videoCodec['bitDepth'] == '10'):
|
||||||
h265 = self.getH265()
|
self.logMsg('Option to transcode 10bit video content enabled.', 1)
|
||||||
try:
|
|
||||||
if not ('h265' in codec or 'hevc' in codec) or (h265 is None):
|
|
||||||
return False
|
|
||||||
# E.g. trailers without a codec of None
|
|
||||||
except TypeError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if resolution >= h265:
|
|
||||||
self.logMsg("Option to transcode h265 enabled. Resolution media: "
|
|
||||||
"%s, transcoding limit resolution: %s"
|
|
||||||
% (resolution, h265), 1)
|
|
||||||
return True
|
return True
|
||||||
|
codec = videoCodec['videocodec']
|
||||||
|
if (utils.settings('transcodeHEVC') == 'true' and codec == 'hevc'):
|
||||||
|
self.logMsg('Option to transcode HEVC video codec enabled.', 1)
|
||||||
|
return True
|
||||||
|
if codec is None:
|
||||||
|
# e.g. trailers. Avoids TypeError with "'h265' in codec"
|
||||||
|
self.logMsg('No codec from PMS, not transcoding.', 1)
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
resolution = int(videoCodec['resolution'])
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
self.logMsg('No video resolution from PMS, not transcoding.', 1)
|
||||||
|
return False
|
||||||
|
if 'h265' in codec:
|
||||||
|
if resolution >= self.getH265():
|
||||||
|
self.logMsg("Option to transcode h265 enabled. Resolution of "
|
||||||
|
"the media: %s, transcoding limit resolution: %s"
|
||||||
|
% (str(resolution), str(self.getH265())), 1)
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def isDirectStream(self):
|
def isDirectStream(self):
|
||||||
|
# Never transcode Music
|
||||||
|
if self.API.getType() == 'track':
|
||||||
|
return True
|
||||||
# set to 'Transcode=2'
|
# set to 'Transcode=2'
|
||||||
if utils.settings('playType') == "2":
|
if utils.settings('playType') == "2":
|
||||||
# User forcing to play via HTTP
|
# User forcing to play via HTTP
|
||||||
self.logMsg("User chose to transcode", 1)
|
self.logMsg("User chose to transcode", 1)
|
||||||
return False
|
return False
|
||||||
if self.h265enabled():
|
if self.mustTranscode():
|
||||||
return False
|
return False
|
||||||
# Verify the bitrate
|
# Verify the bitrate
|
||||||
if not self.isNetworkSufficient():
|
if not self.isNetworkSufficient():
|
||||||
|
@ -207,25 +201,6 @@ class PlayUtils():
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def directStream(self):
|
|
||||||
|
|
||||||
server = self.server
|
|
||||||
|
|
||||||
itemid = self.API.getRatingKey()
|
|
||||||
type = self.API.getType()
|
|
||||||
|
|
||||||
# if 'Path' in item and item['Path'].endswith('.strm'):
|
|
||||||
# # Allow strm loading when direct streaming
|
|
||||||
# playurl = self.directPlay()
|
|
||||||
if type == "Audio":
|
|
||||||
playurl = "%s/emby/Audio/%s/stream.mp3" % (server, itemid)
|
|
||||||
else:
|
|
||||||
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
|
|
||||||
playurl = "{server}/player/playback/playMedia?key=%2Flibrary%2Fmetadata%2F%s&offset=0&X-Plex-Client-Identifier={clientId}&machineIdentifier={SERVER ID}&address={SERVER IP}&port={SERVER PORT}&protocol=http&path=http%3A%2F%2F{SERVER IP}%3A{SERVER PORT}%2Flibrary%2Fmetadata%2F{MEDIA ID}" % (itemid)
|
|
||||||
playurl = self.API.replaceURLtags()
|
|
||||||
|
|
||||||
return playurl
|
|
||||||
|
|
||||||
def isNetworkSufficient(self):
|
def isNetworkSufficient(self):
|
||||||
"""
|
"""
|
||||||
Returns True if the network is sufficient (set in file settings)
|
Returns True if the network is sufficient (set in file settings)
|
||||||
|
@ -243,38 +218,6 @@ class PlayUtils():
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def isTranscoding(self):
|
|
||||||
# I hope Plex transcodes everything
|
|
||||||
return True
|
|
||||||
item = self.item
|
|
||||||
|
|
||||||
canTranscode = item['MediaSources'][0]['SupportsTranscoding']
|
|
||||||
# Make sure the server supports it
|
|
||||||
if not canTranscode:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def transcoding(self):
|
|
||||||
|
|
||||||
item = self.item
|
|
||||||
|
|
||||||
if 'Path' in item and item['Path'].endswith('.strm'):
|
|
||||||
# Allow strm loading when transcoding
|
|
||||||
playurl = self.directPlay()
|
|
||||||
else:
|
|
||||||
itemid = item['Id']
|
|
||||||
deviceId = self.clientInfo.getDeviceId()
|
|
||||||
playurl = (
|
|
||||||
"%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s"
|
|
||||||
% (self.server, itemid, itemid)
|
|
||||||
)
|
|
||||||
playurl = (
|
|
||||||
"%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s"
|
|
||||||
% (playurl, deviceId, self.getBitrate()*1000))
|
|
||||||
|
|
||||||
return playurl
|
|
||||||
|
|
||||||
def getBitrate(self):
|
def getBitrate(self):
|
||||||
# get the addon video quality
|
# get the addon video quality
|
||||||
videoQuality = utils.settings('transcoderVideoQualities')
|
videoQuality = utils.settings('transcoderVideoQualities')
|
||||||
|
@ -295,14 +238,19 @@ class PlayUtils():
|
||||||
return bitrate.get(videoQuality, 2147483)
|
return bitrate.get(videoQuality, 2147483)
|
||||||
|
|
||||||
def getH265(self):
|
def getH265(self):
|
||||||
chosen = utils.settings('transcodeH265')
|
"""
|
||||||
|
Returns the user settings for transcoding h265: boundary resolutions
|
||||||
|
of 480, 720 or 1080 as an int
|
||||||
|
|
||||||
|
OR 9999999 (int) if user chose not to transcode
|
||||||
|
"""
|
||||||
H265 = {
|
H265 = {
|
||||||
'0': None,
|
'0': 9999999,
|
||||||
'1': 480,
|
'1': 480,
|
||||||
'2': 720,
|
'2': 720,
|
||||||
'3': 1080
|
'3': 1080
|
||||||
}
|
}
|
||||||
return H265.get(chosen, None)
|
return H265[utils.settings('transcodeH265')]
|
||||||
|
|
||||||
def getResolution(self):
|
def getResolution(self):
|
||||||
chosen = utils.settings('transcoderVideoQualities')
|
chosen = utils.settings('transcoderVideoQualities')
|
||||||
|
@ -365,7 +313,7 @@ class PlayUtils():
|
||||||
|
|
||||||
#audioStreamsChannelsList[audioNum] = stream.attrib['channels']
|
#audioStreamsChannelsList[audioNum] = stream.attrib['channels']
|
||||||
audioStreamsList.append(index)
|
audioStreamsList.append(index)
|
||||||
audioStreams.append(track.encode('utf-8'))
|
audioStreams.append(utils.tryEncode(track))
|
||||||
audioNum += 1
|
audioNum += 1
|
||||||
|
|
||||||
# Subtitles
|
# Subtitles
|
||||||
|
@ -389,7 +337,7 @@ class PlayUtils():
|
||||||
downloadableStreams.append(index)
|
downloadableStreams.append(index)
|
||||||
|
|
||||||
subtitleStreamsList.append(index)
|
subtitleStreamsList.append(index)
|
||||||
subtitleStreams.append(track.encode('utf-8'))
|
subtitleStreams.append(utils.tryEncode(track))
|
||||||
subNum += 1
|
subNum += 1
|
||||||
|
|
||||||
if audioNum > 1:
|
if audioNum > 1:
|
||||||
|
@ -421,7 +369,7 @@ class PlayUtils():
|
||||||
% (self.server, selectSubsIndex)
|
% (self.server, selectSubsIndex)
|
||||||
url = self.API.addPlexHeadersToUrl(url)
|
url = self.API.addPlexHeadersToUrl(url)
|
||||||
self.logMsg("Downloadable sub: %s: %s" % (selectSubsIndex, url), 1)
|
self.logMsg("Downloadable sub: %s: %s" % (selectSubsIndex, url), 1)
|
||||||
listitem.setSubtitles([url.encode('utf-8')])
|
listitem.setSubtitles([utils.tryEncode(url)])
|
||||||
else:
|
else:
|
||||||
self.logMsg('Need to burn in subtitle %s' % selectSubsIndex, 1)
|
self.logMsg('Need to burn in subtitle %s' % selectSubsIndex, 1)
|
||||||
playurlprefs["subtitleStreamID"] = selectSubsIndex
|
playurlprefs["subtitleStreamID"] = selectSubsIndex
|
||||||
|
|
|
@ -5,6 +5,7 @@ import string
|
||||||
import xbmc
|
import xbmc
|
||||||
|
|
||||||
from utils import logging
|
from utils import logging
|
||||||
|
import embydb_functions as embydb
|
||||||
|
|
||||||
|
|
||||||
def xbmc_photo():
|
def xbmc_photo():
|
||||||
|
@ -58,7 +59,9 @@ def getOKMsg():
|
||||||
|
|
||||||
|
|
||||||
def timeToMillis(time):
|
def timeToMillis(time):
|
||||||
return (time['hours']*3600 + time['minutes']*60 + time['seconds'])*1000 + time['milliseconds']
|
return (time['hours']*3600 +
|
||||||
|
time['minutes']*60 +
|
||||||
|
time['seconds'])*1000 + time['milliseconds']
|
||||||
|
|
||||||
|
|
||||||
def millisToTime(t):
|
def millisToTime(t):
|
||||||
|
@ -69,7 +72,10 @@ def millisToTime(t):
|
||||||
seconds = seconds % 60
|
seconds = seconds % 60
|
||||||
minutes = minutes % 60
|
minutes = minutes % 60
|
||||||
millis = millis % 1000
|
millis = millis % 1000
|
||||||
return {'hours':hours,'minutes':minutes,'seconds':seconds,'milliseconds':millis}
|
return {'hours': hours,
|
||||||
|
'minutes': minutes,
|
||||||
|
'seconds': seconds,
|
||||||
|
'milliseconds': millis}
|
||||||
|
|
||||||
|
|
||||||
def textFromXml(element):
|
def textFromXml(element):
|
||||||
|
@ -86,44 +92,70 @@ class jsonClass():
|
||||||
def jsonrpc(self, action, arguments={}):
|
def jsonrpc(self, action, arguments={}):
|
||||||
""" put some JSON together for the JSON-RPC APIv6 """
|
""" put some JSON together for the JSON-RPC APIv6 """
|
||||||
if action.lower() == "sendkey":
|
if action.lower() == "sendkey":
|
||||||
request=json.dumps({ "jsonrpc" : "2.0" , "method" : "Input.SendText", "params" : { "text" : arguments[0], "done" : False }} )
|
request = json.dumps({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "Input.SendText",
|
||||||
|
"params": {
|
||||||
|
"text": arguments[0],
|
||||||
|
"done": False
|
||||||
|
}
|
||||||
|
})
|
||||||
elif action.lower() == "ping":
|
elif action.lower() == "ping":
|
||||||
request=json.dumps({ "jsonrpc" : "2.0",
|
request = json.dumps({
|
||||||
"id" : 1 ,
|
"jsonrpc": "2.0",
|
||||||
"method" : "JSONRPC.Ping" })
|
"id": 1,
|
||||||
elif action.lower() == "playmedia":
|
"method": "JSONRPC.Ping"
|
||||||
xbmc.Player().play("plugin://plugin.video.plexkodiconnect/"
|
})
|
||||||
"?mode=companion&arguments=%s"
|
|
||||||
% arguments)
|
|
||||||
return True
|
|
||||||
elif arguments:
|
elif arguments:
|
||||||
request=json.dumps({ "id" : 1,
|
request = json.dumps({
|
||||||
"jsonrpc" : "2.0",
|
"id": 1,
|
||||||
"method" : action,
|
"jsonrpc": "2.0",
|
||||||
"params" : arguments})
|
"method": action,
|
||||||
|
"params": arguments})
|
||||||
else:
|
else:
|
||||||
request=json.dumps({ "id" : 1,
|
request = json.dumps({
|
||||||
"jsonrpc" : "2.0",
|
"id": 1,
|
||||||
"method" : action})
|
"jsonrpc": "2.0",
|
||||||
|
"method": action
|
||||||
|
})
|
||||||
|
|
||||||
result = self.parseJSONRPC(xbmc.executeJSONRPC(request))
|
result = self.parseJSONRPC(xbmc.executeJSONRPC(request))
|
||||||
|
|
||||||
if not result and self.settings['webserver_enabled']:
|
if not result and self.settings['webserver_enabled']:
|
||||||
# xbmc.executeJSONRPC appears to fail on the login screen, but going
|
# xbmc.executeJSONRPC appears to fail on the login screen, but
|
||||||
# through the network stack works, so let's try the request again
|
# going through the network stack works, so let's try the request
|
||||||
|
# again
|
||||||
result = self.parseJSONRPC(self.requestMgr.post(
|
result = self.parseJSONRPC(self.requestMgr.post(
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
self.settings['port'],
|
self.settings['port'],
|
||||||
"/jsonrpc",
|
"/jsonrpc",
|
||||||
request,
|
request,
|
||||||
{ 'Content-Type' : 'application/json',
|
{'Content-Type': 'application/json',
|
||||||
'Authorization' : 'Basic ' + string.strip(base64.encodestring(self.settings['user'] + ':' + self.settings['passwd'])) }))
|
'Authorization': 'Basic %s' % string.strip(
|
||||||
|
base64.encodestring('%s:%s'
|
||||||
|
% (self.settings['user'],
|
||||||
|
self.settings['passwd'])))
|
||||||
|
}))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def skipTo(self, plexId, typus):
|
||||||
|
self.logMsg('players: %s' % self.getPlayers())
|
||||||
|
# playlistId = self.getPlaylistId(tryDecode(xbmc_type(typus)))
|
||||||
|
# playerId = self.
|
||||||
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
|
embydb_item = emby_db.getItem_byId(plexId)
|
||||||
|
try:
|
||||||
|
dbid = embydb_item[0]
|
||||||
|
mediatype = embydb_item[4]
|
||||||
|
except TypeError:
|
||||||
|
self.logMsg('Couldnt find item %s in Kodi db' % plexId, 1)
|
||||||
|
return
|
||||||
|
self.logMsg('plexid: %s, kodi id: %s, type: %s'
|
||||||
|
% (plexId, dbid, mediatype))
|
||||||
|
|
||||||
def getPlexHeaders(self):
|
def getPlexHeaders(self):
|
||||||
h = {
|
h = {
|
||||||
"Content-type": "application/x-www-form-urlencoded",
|
"Content-type": "text/xml",
|
||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": "*",
|
||||||
"X-Plex-Version": self.settings['version'],
|
"X-Plex-Version": self.settings['version'],
|
||||||
"X-Plex-Client-Identifier": self.settings['uuid'],
|
"X-Plex-Client-Identifier": self.settings['uuid'],
|
||||||
|
@ -143,7 +175,7 @@ class jsonClass():
|
||||||
self.logMsg("Empty response from XBMC", 1)
|
self.logMsg("Empty response from XBMC", 1)
|
||||||
return {}
|
return {}
|
||||||
else:
|
else:
|
||||||
parsed=json.loads(jsonraw)
|
parsed = json.loads(jsonraw)
|
||||||
if parsed.get('error', False):
|
if parsed.get('error', False):
|
||||||
self.logMsg("XBMC returned an error: %s" % parsed.get('error'), -1)
|
self.logMsg("XBMC returned an error: %s" % parsed.get('error'), -1)
|
||||||
return parsed.get('result', {})
|
return parsed.get('result', {})
|
||||||
|
@ -156,6 +188,27 @@ class jsonClass():
|
||||||
ret[player['type']] = player
|
ret[player['type']] = player
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def getPlaylistId(self, typus):
|
||||||
|
"""
|
||||||
|
typus: one of the Kodi types, e.g. audio or video
|
||||||
|
|
||||||
|
Returns None if nothing was found
|
||||||
|
"""
|
||||||
|
for playlist in self.getPlaylists():
|
||||||
|
if playlist.get('type') == typus:
|
||||||
|
return playlist.get('playlistid')
|
||||||
|
|
||||||
|
def getPlaylists(self):
|
||||||
|
"""
|
||||||
|
Returns a list, e.g.
|
||||||
|
[
|
||||||
|
{u'playlistid': 0, u'type': u'audio'},
|
||||||
|
{u'playlistid': 1, u'type': u'video'},
|
||||||
|
{u'playlistid': 2, u'type': u'picture'}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
return self.jsonrpc('Playlist.GetPlaylists')
|
||||||
|
|
||||||
def getPlayerIds(self):
|
def getPlayerIds(self):
|
||||||
ret = []
|
ret = []
|
||||||
for player in self.getPlayers().values():
|
for player in self.getPlayers().values():
|
||||||
|
@ -178,7 +231,10 @@ class jsonClass():
|
||||||
return players.get(xbmc_photo(), {}).get('playerid', None)
|
return players.get(xbmc_photo(), {}).get('playerid', None)
|
||||||
|
|
||||||
def getVolume(self):
|
def getVolume(self):
|
||||||
answ = self.jsonrpc('Application.GetProperties', { "properties": [ "volume", 'muted' ] })
|
answ = self.jsonrpc('Application.GetProperties',
|
||||||
|
{
|
||||||
|
"properties": ["volume", 'muted']
|
||||||
|
})
|
||||||
vol = str(answ.get('volume', 100))
|
vol = str(answ.get('volume', 100))
|
||||||
mute = ("0", "1")[answ.get('muted', False)]
|
mute = ("0", "1")[answ.get('muted', False)]
|
||||||
return (vol, mute)
|
return (vol, mute)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import re
|
import re
|
||||||
import traceback
|
|
||||||
from SocketServer import ThreadingMixIn
|
from SocketServer import ThreadingMixIn
|
||||||
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
||||||
from urlparse import urlparse, parse_qs
|
from urlparse import urlparse, parse_qs
|
||||||
|
@ -13,6 +12,7 @@ from utils import logging
|
||||||
@logging
|
@logging
|
||||||
class MyHandler(BaseHTTPRequestHandler):
|
class MyHandler(BaseHTTPRequestHandler):
|
||||||
protocol_version = 'HTTP/1.1'
|
protocol_version = 'HTTP/1.1'
|
||||||
|
regex = re.compile(r'''/playQueues/(\d+)$''')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
|
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
|
||||||
|
@ -38,18 +38,20 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
def do_OPTIONS(self):
|
def do_OPTIONS(self):
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header('Content-Length', '0')
|
self.send_header('Content-Length', '0')
|
||||||
self.send_header('X-Plex-Client-Identifier', self.server.settings['uuid'])
|
self.send_header('X-Plex-Client-Identifier',
|
||||||
|
self.server.settings['uuid'])
|
||||||
self.send_header('Content-Type', 'text/plain')
|
self.send_header('Content-Type', 'text/plain')
|
||||||
self.send_header('Connection', 'close')
|
self.send_header('Connection', 'close')
|
||||||
self.send_header('Access-Control-Max-Age', '1209600')
|
self.send_header('Access-Control-Max-Age', '1209600')
|
||||||
self.send_header('Access-Control-Allow-Origin', '*')
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
self.send_header('Access-Control-Allow-Methods',
|
self.send_header('Access-Control-Allow-Methods',
|
||||||
'POST, GET, OPTIONS, DELETE, PUT, HEAD')
|
'POST, GET, OPTIONS, DELETE, PUT, HEAD')
|
||||||
self.send_header('Access-Control-Allow-Headers',
|
self.send_header(
|
||||||
'x-plex-version, x-plex-platform-version, '
|
'Access-Control-Allow-Headers',
|
||||||
'x-plex-username, x-plex-client-identifier, '
|
'x-plex-version, x-plex-platform-version, x-plex-username, '
|
||||||
'x-plex-target-client-identifier, x-plex-device-name, '
|
'x-plex-client-identifier, x-plex-target-client-identifier, '
|
||||||
'x-plex-platform, x-plex-product, accept, x-plex-device')
|
'x-plex-device-name, x-plex-platform, x-plex-product, accept, '
|
||||||
|
'x-plex-device')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.close()
|
self.wfile.close()
|
||||||
|
|
||||||
|
@ -71,9 +73,10 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
def answer_request(self, sendData):
|
def answer_request(self, sendData):
|
||||||
self.serverlist = self.server.client.getServerList()
|
self.serverlist = self.server.client.getServerList()
|
||||||
self.subMgr = self.server.subscriptionManager
|
subMgr = self.server.subscriptionManager
|
||||||
self.js = self.server.jsonClass
|
js = self.server.jsonClass
|
||||||
self.settings = self.server.settings
|
settings = self.server.settings
|
||||||
|
queue = self.server.queue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
request_path = self.path[1:]
|
request_path = self.path[1:]
|
||||||
|
@ -83,60 +86,82 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
params = {}
|
params = {}
|
||||||
for key in paramarrays:
|
for key in paramarrays:
|
||||||
params[key] = paramarrays[key][0]
|
params[key] = paramarrays[key][0]
|
||||||
|
self.logMsg("remote request_path: %s" % request_path, 2)
|
||||||
self.logMsg("params received from remote: %s" % params, 2)
|
self.logMsg("params received from remote: %s" % params, 2)
|
||||||
self.subMgr.updateCommandID(self.headers.get('X-Plex-Client-Identifier', self.client_address[0]), params.get('commandID', False))
|
subMgr.updateCommandID(self.headers.get(
|
||||||
if request_path=="version":
|
'X-Plex-Client-Identifier',
|
||||||
self.response("PleXBMC Helper Remote Redirector: Running\r\nVersion: %s" % self.settings['version'])
|
self.client_address[0]),
|
||||||
elif request_path=="verify":
|
params.get('commandID', False))
|
||||||
result=self.js.jsonrpc("ping")
|
if request_path == "version":
|
||||||
self.response("XBMC JSON connection test:\r\n"+result)
|
self.response(
|
||||||
|
"PlexKodiConnect Plex Companion: Running\r\nVersion: %s"
|
||||||
|
% settings['version'])
|
||||||
|
elif request_path == "verify":
|
||||||
|
self.response("XBMC JSON connection test:\r\n" +
|
||||||
|
js.jsonrpc("ping"))
|
||||||
elif "resources" == request_path:
|
elif "resources" == request_path:
|
||||||
resp = getXMLHeader()
|
resp = ('%s'
|
||||||
resp += "<MediaContainer>"
|
'<MediaContainer>'
|
||||||
resp += "<Player"
|
'<Player'
|
||||||
resp += ' title="%s"' % self.settings['client_name']
|
' title="%s"'
|
||||||
resp += ' protocol="plex"'
|
' protocol="plex"'
|
||||||
resp += ' protocolVersion="1"'
|
' protocolVersion="1"'
|
||||||
resp += ' protocolCapabilities="navigation,playback,timeline"'
|
' protocolCapabilities="navigation,playback,timeline"'
|
||||||
resp += ' machineIdentifier="%s"' % self.settings['uuid']
|
' machineIdentifier="%s"'
|
||||||
resp += ' product="PlexKodiConnect"'
|
' product="PlexKodiConnect"'
|
||||||
resp += ' platform="%s"' % self.settings['platform']
|
' platform="%s"'
|
||||||
resp += ' platformVersion="%s"' % self.settings['plexbmc_version']
|
' platformVersion="%s"'
|
||||||
resp += ' deviceClass="pc"'
|
' deviceClass="pc"'
|
||||||
resp += "/>"
|
'/>'
|
||||||
resp += "</MediaContainer>"
|
'</MediaContainer>'
|
||||||
|
% (getXMLHeader(),
|
||||||
|
settings['client_name'],
|
||||||
|
settings['uuid'],
|
||||||
|
settings['platform'],
|
||||||
|
settings['plexbmc_version']))
|
||||||
self.logMsg("crafted resources response: %s" % resp, 2)
|
self.logMsg("crafted resources response: %s" % resp, 2)
|
||||||
self.response(resp, self.js.getPlexHeaders())
|
self.response(resp, js.getPlexHeaders())
|
||||||
elif "/subscribe" in request_path:
|
elif "/subscribe" in request_path:
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
protocol = params.get('protocol', False)
|
protocol = params.get('protocol', False)
|
||||||
host = self.client_address[0]
|
host = self.client_address[0]
|
||||||
port = params.get('port', False)
|
port = params.get('port', False)
|
||||||
uuid = self.headers.get('X-Plex-Client-Identifier', "")
|
uuid = self.headers.get('X-Plex-Client-Identifier', "")
|
||||||
commandID = params.get('commandID', 0)
|
commandID = params.get('commandID', 0)
|
||||||
self.subMgr.addSubscriber(protocol, host, port, uuid, commandID)
|
subMgr.addSubscriber(protocol,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
uuid,
|
||||||
|
commandID)
|
||||||
elif "/poll" in request_path:
|
elif "/poll" in request_path:
|
||||||
if params.get('wait', False) == '1':
|
if params.get('wait', False) == '1':
|
||||||
sleep(950)
|
sleep(950)
|
||||||
commandID = params.get('commandID', 0)
|
commandID = params.get('commandID', 0)
|
||||||
self.response(re.sub(r"INSERTCOMMANDID", str(commandID), self.subMgr.msg(self.js.getPlayers())), {
|
self.response(
|
||||||
'X-Plex-Client-Identifier': self.settings['uuid'],
|
re.sub(r"INSERTCOMMANDID",
|
||||||
'Access-Control-Expose-Headers': 'X-Plex-Client-Identifier',
|
str(commandID),
|
||||||
'Access-Control-Allow-Origin': '*',
|
subMgr.msg(js.getPlayers())),
|
||||||
'Content-Type': 'text/xml'
|
{
|
||||||
})
|
'X-Plex-Client-Identifier': settings['uuid'],
|
||||||
|
'Access-Control-Expose-Headers':
|
||||||
|
'X-Plex-Client-Identifier',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Type': 'text/xml'
|
||||||
|
})
|
||||||
elif "/unsubscribe" in request_path:
|
elif "/unsubscribe" in request_path:
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
uuid = self.headers.get('X-Plex-Client-Identifier', False) or self.client_address[0]
|
uuid = self.headers.get('X-Plex-Client-Identifier', False) \
|
||||||
self.subMgr.removeSubscriber(uuid)
|
or self.client_address[0]
|
||||||
|
subMgr.removeSubscriber(uuid)
|
||||||
elif request_path == "player/playback/setParameters":
|
elif request_path == "player/playback/setParameters":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
if 'volume' in params:
|
if 'volume' in params:
|
||||||
volume = int(params['volume'])
|
volume = int(params['volume'])
|
||||||
self.logMsg("adjusting the volume to %s%%" % volume, 2)
|
self.logMsg("adjusting the volume to %s%%" % volume, 2)
|
||||||
self.js.jsonrpc("Application.SetVolume", {"volume": volume})
|
js.jsonrpc("Application.SetVolume",
|
||||||
|
{"volume": volume})
|
||||||
elif "/playMedia" in request_path:
|
elif "/playMedia" in request_path:
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
offset = params.get('viewOffset', params.get('offset', "0"))
|
offset = params.get('viewOffset', params.get('offset', "0"))
|
||||||
protocol = params.get('protocol', "http")
|
protocol = params.get('protocol', "http")
|
||||||
address = params.get('address', self.client_address[0])
|
address = params.get('address', self.client_address[0])
|
||||||
|
@ -146,78 +171,102 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
containerKey = urlparse(params.get('containerKey')).path
|
containerKey = urlparse(params.get('containerKey')).path
|
||||||
except:
|
except:
|
||||||
containerKey = ''
|
containerKey = ''
|
||||||
regex = re.compile(r'''/playQueues/(\d+)$''')
|
|
||||||
try:
|
try:
|
||||||
playQueueID = regex.findall(containerKey)[0]
|
playQueueID = self.regex.findall(containerKey)[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
playQueueID = ''
|
playQueueID = ''
|
||||||
|
# We need to tell service.py
|
||||||
self.js.jsonrpc("playmedia", params)
|
queue.put({
|
||||||
self.subMgr.lastkey = params['key']
|
'action': 'playlist',
|
||||||
self.subMgr.containerKey = containerKey
|
'data': params
|
||||||
self.subMgr.playQueueID = playQueueID
|
})
|
||||||
self.subMgr.server = server.get('server', 'localhost')
|
subMgr.lastkey = params['key']
|
||||||
self.subMgr.port = port
|
subMgr.containerKey = containerKey
|
||||||
self.subMgr.protocol = protocol
|
subMgr.playQueueID = playQueueID
|
||||||
self.subMgr.notify()
|
subMgr.server = server.get('server', 'localhost')
|
||||||
|
subMgr.port = port
|
||||||
|
subMgr.protocol = protocol
|
||||||
|
subMgr.notify()
|
||||||
elif request_path == "player/playback/play":
|
elif request_path == "player/playback/play":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
for playerid in self.js.getPlayerIds():
|
for playerid in js.getPlayerIds():
|
||||||
self.js.jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": True})
|
js.jsonrpc("Player.PlayPause",
|
||||||
|
{"playerid": playerid, "play": True})
|
||||||
|
subMgr.notify()
|
||||||
elif request_path == "player/playback/pause":
|
elif request_path == "player/playback/pause":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
for playerid in self.js.getPlayerIds():
|
for playerid in js.getPlayerIds():
|
||||||
self.js.jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": False})
|
js.jsonrpc("Player.PlayPause",
|
||||||
|
{"playerid": playerid, "play": False})
|
||||||
|
subMgr.notify()
|
||||||
elif request_path == "player/playback/stop":
|
elif request_path == "player/playback/stop":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
for playerid in self.js.getPlayerIds():
|
for playerid in js.getPlayerIds():
|
||||||
self.js.jsonrpc("Player.Stop", {"playerid" : playerid})
|
js.jsonrpc("Player.Stop", {"playerid": playerid})
|
||||||
|
subMgr.notify()
|
||||||
elif request_path == "player/playback/seekTo":
|
elif request_path == "player/playback/seekTo":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
for playerid in self.js.getPlayerIds():
|
for playerid in js.getPlayerIds():
|
||||||
self.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":millisToTime(params.get('offset', 0))})
|
js.jsonrpc("Player.Seek",
|
||||||
self.subMgr.notify()
|
{"playerid": playerid,
|
||||||
|
"value": millisToTime(
|
||||||
|
params.get('offset', 0))})
|
||||||
|
subMgr.notify()
|
||||||
elif request_path == "player/playback/stepForward":
|
elif request_path == "player/playback/stepForward":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
for playerid in self.js.getPlayerIds():
|
for playerid in js.getPlayerIds():
|
||||||
self.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallforward"})
|
js.jsonrpc("Player.Seek",
|
||||||
self.subMgr.notify()
|
{"playerid": playerid,
|
||||||
|
"value": "smallforward"})
|
||||||
|
subMgr.notify()
|
||||||
elif request_path == "player/playback/stepBack":
|
elif request_path == "player/playback/stepBack":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
for playerid in self.js.getPlayerIds():
|
for playerid in js.getPlayerIds():
|
||||||
self.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallbackward"})
|
js.jsonrpc("Player.Seek",
|
||||||
self.subMgr.notify()
|
{"playerid": playerid,
|
||||||
|
"value": "smallbackward"})
|
||||||
|
subMgr.notify()
|
||||||
elif request_path == "player/playback/skipNext":
|
elif request_path == "player/playback/skipNext":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
for playerid in self.js.getPlayerIds():
|
for playerid in js.getPlayerIds():
|
||||||
self.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigforward"})
|
js.jsonrpc("Player.GoTo",
|
||||||
self.subMgr.notify()
|
{"playerid": playerid,
|
||||||
|
"to": "next"})
|
||||||
|
subMgr.notify()
|
||||||
elif request_path == "player/playback/skipPrevious":
|
elif request_path == "player/playback/skipPrevious":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
for playerid in self.js.getPlayerIds():
|
for playerid in js.getPlayerIds():
|
||||||
self.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigbackward"})
|
js.jsonrpc("Player.GoTo",
|
||||||
self.subMgr.notify()
|
{"playerid": playerid,
|
||||||
|
"to": "previous"})
|
||||||
|
subMgr.notify()
|
||||||
|
elif request_path == "player/playback/skipTo":
|
||||||
|
js.skipTo(params.get('key').rsplit('/', 1)[1],
|
||||||
|
params.get('type'))
|
||||||
|
subMgr.notify()
|
||||||
elif request_path == "player/navigation/moveUp":
|
elif request_path == "player/navigation/moveUp":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
self.js.jsonrpc("Input.Up")
|
js.jsonrpc("Input.Up")
|
||||||
elif request_path == "player/navigation/moveDown":
|
elif request_path == "player/navigation/moveDown":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
self.js.jsonrpc("Input.Down")
|
js.jsonrpc("Input.Down")
|
||||||
elif request_path == "player/navigation/moveLeft":
|
elif request_path == "player/navigation/moveLeft":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
self.js.jsonrpc("Input.Left")
|
js.jsonrpc("Input.Left")
|
||||||
elif request_path == "player/navigation/moveRight":
|
elif request_path == "player/navigation/moveRight":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
self.js.jsonrpc("Input.Right")
|
js.jsonrpc("Input.Right")
|
||||||
elif request_path == "player/navigation/select":
|
elif request_path == "player/navigation/select":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
self.js.jsonrpc("Input.Select")
|
js.jsonrpc("Input.Select")
|
||||||
elif request_path == "player/navigation/home":
|
elif request_path == "player/navigation/home":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
self.js.jsonrpc("Input.Home")
|
js.jsonrpc("Input.Home")
|
||||||
elif request_path == "player/navigation/back":
|
elif request_path == "player/navigation/back":
|
||||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
self.response(getOKMsg(), js.getPlexHeaders())
|
||||||
self.js.jsonrpc("Input.Back")
|
js.jsonrpc("Input.Back")
|
||||||
|
else:
|
||||||
|
self.logMsg('Unknown request path: %s' % request_path, -1)
|
||||||
# elif 'player/mirror/details' in request_path:
|
# elif 'player/mirror/details' in request_path:
|
||||||
# # Detailed e.g. Movie information page was opened
|
# # Detailed e.g. Movie information page was opened
|
||||||
# # CURRENTLY NOT POSSIBLE DUE TO KODI RESTRICTIONS
|
# # CURRENTLY NOT POSSIBLE DUE TO KODI RESTRICTIONS
|
||||||
|
@ -240,6 +289,7 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
except:
|
except:
|
||||||
self.logMsg('Error encountered. Traceback:', -1)
|
self.logMsg('Error encountered. Traceback:', -1)
|
||||||
|
import traceback
|
||||||
self.logMsg(traceback.print_exc(), -1)
|
self.logMsg(traceback.print_exc(), -1)
|
||||||
|
|
||||||
|
|
||||||
|
@ -247,7 +297,7 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
||||||
daemon_threads = True
|
daemon_threads = True
|
||||||
|
|
||||||
def __init__(self, client, subscriptionManager, jsonClass, settings,
|
def __init__(self, client, subscriptionManager, jsonClass, settings,
|
||||||
*args, **kwargs):
|
queue, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
client: Class handle to plexgdm.plexgdm. We can thus ask for an up-to-
|
client: Class handle to plexgdm.plexgdm. We can thus ask for an up-to-
|
||||||
date serverlist without instantiating anything
|
date serverlist without instantiating anything
|
||||||
|
@ -258,4 +308,5 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
||||||
self.subscriptionManager = subscriptionManager
|
self.subscriptionManager = subscriptionManager
|
||||||
self.jsonClass = jsonClass
|
self.jsonClass = jsonClass
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
|
self.queue = queue
|
||||||
HTTPServer.__init__(self, *args, **kwargs)
|
HTTPServer.__init__(self, *args, **kwargs)
|
||||||
|
|
|
@ -22,18 +22,15 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
MA 02110-1301, USA.
|
MA 02110-1301, USA.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__author__ = 'DHJ (hippojay) <plex@h-jay.com>'
|
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
import struct
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from xbmc import sleep
|
from xbmc import sleep
|
||||||
|
|
||||||
import downloadutils
|
import downloadutils
|
||||||
from PlexFunctions import PMSHttpsEnabled
|
from utils import window, logging, settings
|
||||||
from utils import window, logging
|
|
||||||
|
|
||||||
|
|
||||||
@logging
|
@logging
|
||||||
|
@ -60,7 +57,7 @@ class plexgdm:
|
||||||
self.client_registered = False
|
self.client_registered = False
|
||||||
self.download = downloadutils.DownloadUtils().downloadUrl
|
self.download = downloadutils.DownloadUtils().downloadUrl
|
||||||
|
|
||||||
def clientDetails(self, settings):
|
def clientDetails(self, options):
|
||||||
self.client_data = (
|
self.client_data = (
|
||||||
"Content-Type: plex/media-player\r\n"
|
"Content-Type: plex/media-player\r\n"
|
||||||
"Resource-Identifier: %s\r\n"
|
"Resource-Identifier: %s\r\n"
|
||||||
|
@ -74,13 +71,13 @@ class plexgdm:
|
||||||
"mirror,playqueues\r\n"
|
"mirror,playqueues\r\n"
|
||||||
"Device-Class: HTPC"
|
"Device-Class: HTPC"
|
||||||
) % (
|
) % (
|
||||||
settings['uuid'],
|
options['uuid'],
|
||||||
settings['client_name'],
|
options['client_name'],
|
||||||
settings['myport'],
|
options['myport'],
|
||||||
settings['addonName'],
|
options['addonName'],
|
||||||
settings['version']
|
options['version']
|
||||||
)
|
)
|
||||||
self.client_id = settings['uuid']
|
self.client_id = options['uuid']
|
||||||
|
|
||||||
def getClientDetails(self):
|
def getClientDetails(self):
|
||||||
if not self.client_data:
|
if not self.client_data:
|
||||||
|
@ -215,119 +212,28 @@ class plexgdm:
|
||||||
return self.server_list
|
return self.server_list
|
||||||
|
|
||||||
def discover(self):
|
def discover(self):
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
||||||
|
|
||||||
# Set a timeout so the socket does not block indefinitely
|
|
||||||
sock.settimeout(0.6)
|
|
||||||
|
|
||||||
# Set the time-to-live for messages to 1 for local network
|
|
||||||
ttl = struct.pack('b', 1)
|
|
||||||
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
|
|
||||||
|
|
||||||
returnData = []
|
|
||||||
try:
|
|
||||||
# Send data to the multicast group
|
|
||||||
self.logMsg("Sending discovery messages: %s"
|
|
||||||
% self.discover_message, 2)
|
|
||||||
sock.sendto(self.discover_message, self.discover_group)
|
|
||||||
|
|
||||||
# Look for responses from all recipients
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
data, server = sock.recvfrom(1024)
|
|
||||||
self.logMsg("Received data from %s, %s" % server, 2)
|
|
||||||
returnData.append({'from': server,
|
|
||||||
'data': data})
|
|
||||||
except socket.timeout:
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
# if we can't send our discovery query, just abort and try again
|
|
||||||
# on the next loop
|
|
||||||
return
|
|
||||||
finally:
|
|
||||||
sock.close()
|
|
||||||
|
|
||||||
self.discovery_complete = True
|
|
||||||
|
|
||||||
discovered_servers = []
|
|
||||||
|
|
||||||
if returnData:
|
|
||||||
|
|
||||||
for response in returnData:
|
|
||||||
update = {'server': response.get('from')[0]}
|
|
||||||
|
|
||||||
# Check if we had a positive HTTP reponse
|
|
||||||
if "200 OK" in response.get('data'):
|
|
||||||
for each in response.get('data').split('\r\n'):
|
|
||||||
update['discovery'] = "auto"
|
|
||||||
update['owned'] = '1'
|
|
||||||
update['master'] = 1
|
|
||||||
update['role'] = 'master'
|
|
||||||
update['class'] = None
|
|
||||||
if "Content-Type:" in each:
|
|
||||||
update['content-type'] = each.split(':')[1].strip()
|
|
||||||
elif "Resource-Identifier:" in each:
|
|
||||||
update['uuid'] = each.split(':')[1].strip()
|
|
||||||
elif "Name:" in each:
|
|
||||||
update['serverName'] = each.split(':')[1].strip()
|
|
||||||
elif "Port:" in each:
|
|
||||||
update['port'] = each.split(':')[1].strip()
|
|
||||||
elif "Updated-At:" in each:
|
|
||||||
update['updated'] = each.split(':')[1].strip()
|
|
||||||
elif "Version:" in each:
|
|
||||||
update['version'] = each.split(':')[1].strip()
|
|
||||||
elif "Server-Class:" in each:
|
|
||||||
update['class'] = each.split(':')[1].strip()
|
|
||||||
|
|
||||||
# Quickly test if we need https
|
|
||||||
https = PMSHttpsEnabled(
|
|
||||||
'%s:%s' % (update['server'], update['port']))
|
|
||||||
if https is None:
|
|
||||||
# Error contacting server
|
|
||||||
continue
|
|
||||||
elif https:
|
|
||||||
update['protocol'] = 'https'
|
|
||||||
else:
|
|
||||||
update['protocol'] = 'http'
|
|
||||||
discovered_servers.append(update)
|
|
||||||
|
|
||||||
# Append REMOTE PMS that we haven't found yet; if necessary
|
|
||||||
currServer = window('pms_server')
|
currServer = window('pms_server')
|
||||||
if currServer:
|
if not currServer:
|
||||||
currServerProt, currServerIP, currServerPort = \
|
return
|
||||||
currServer.split(':')
|
currServerProt, currServerIP, currServerPort = \
|
||||||
currServerIP = currServerIP.replace('/', '')
|
currServer.split(':')
|
||||||
for server in discovered_servers:
|
currServerIP = currServerIP.replace('/', '')
|
||||||
if server['server'] == currServerIP:
|
# Currently active server was not discovered via GDM; ADD
|
||||||
break
|
self.server_list = [{
|
||||||
else:
|
'port': currServerPort,
|
||||||
# Currently active server was not discovered via GDM; ADD
|
'protocol': currServerProt,
|
||||||
update = {
|
'class': None,
|
||||||
'port': currServerPort,
|
'content-type': 'plex/media-server',
|
||||||
'protocol': currServerProt,
|
'discovery': 'auto',
|
||||||
'class': None,
|
'master': 1,
|
||||||
'content-type': 'plex/media-server',
|
'owned': '1',
|
||||||
'discovery': 'auto',
|
'role': 'master',
|
||||||
'master': 1,
|
'server': currServerIP,
|
||||||
'owned': '1',
|
'serverName': window('plex_servername'),
|
||||||
'role': 'master',
|
'updated': int(time.time()),
|
||||||
'server': currServerIP,
|
'uuid': window('plex_machineIdentifier'),
|
||||||
'serverName': window('plex_servername'),
|
'version': 'irrelevant'
|
||||||
'updated': int(time.time()),
|
}]
|
||||||
'uuid': window('plex_machineIdentifier'),
|
|
||||||
'version': 'irrelevant'
|
|
||||||
}
|
|
||||||
discovered_servers.append(update)
|
|
||||||
|
|
||||||
self.server_list = discovered_servers
|
|
||||||
|
|
||||||
if not self.server_list:
|
|
||||||
self.logMsg("No servers have been discovered", 0)
|
|
||||||
else:
|
|
||||||
self.logMsg("Number of servers Discovered: %s"
|
|
||||||
% len(self.server_list), 2)
|
|
||||||
for items in self.server_list:
|
|
||||||
self.logMsg("Server Discovered: %s" % items, 2)
|
|
||||||
|
|
||||||
def setInterval(self, interval):
|
def setInterval(self, interval):
|
||||||
self.discovery_interval = interval
|
self.discovery_interval = interval
|
||||||
|
@ -388,4 +294,5 @@ class plexgdm:
|
||||||
|
|
||||||
def start_all(self, daemon=False):
|
def start_all(self, daemon=False):
|
||||||
self.start_discovery(daemon)
|
self.start_discovery(daemon)
|
||||||
self.start_registration(daemon)
|
if settings('plexCompanion') == 'true':
|
||||||
|
self.start_registration(daemon)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from xbmc import Player
|
|
||||||
|
|
||||||
import downloadutils
|
import downloadutils
|
||||||
from utils import window, logging
|
from utils import window, logging
|
||||||
import PlexFunctions as pf
|
import PlexFunctions as pf
|
||||||
|
@ -11,13 +9,19 @@ from functions import *
|
||||||
|
|
||||||
@logging
|
@logging
|
||||||
class SubscriptionManager:
|
class SubscriptionManager:
|
||||||
def __init__(self, jsonClass, RequestMgr):
|
def __init__(self, jsonClass, RequestMgr, player, playlist):
|
||||||
self.serverlist = []
|
self.serverlist = []
|
||||||
self.subscribers = {}
|
self.subscribers = {}
|
||||||
self.info = {}
|
self.info = {}
|
||||||
self.lastkey = ""
|
self.lastkey = ""
|
||||||
self.containerKey = ""
|
self.containerKey = ""
|
||||||
self.lastratingkey = ""
|
self.ratingkey = ""
|
||||||
|
self.lastplayers = {}
|
||||||
|
self.lastinfo = {
|
||||||
|
'video': {},
|
||||||
|
'audio': {},
|
||||||
|
'picture': {}
|
||||||
|
}
|
||||||
self.volume = 0
|
self.volume = 0
|
||||||
self.mute = '0'
|
self.mute = '0'
|
||||||
self.server = ""
|
self.server = ""
|
||||||
|
@ -25,7 +29,8 @@ class SubscriptionManager:
|
||||||
self.port = ""
|
self.port = ""
|
||||||
self.playerprops = {}
|
self.playerprops = {}
|
||||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||||
self.xbmcplayer = Player()
|
self.xbmcplayer = player
|
||||||
|
self.playlist = playlist
|
||||||
|
|
||||||
self.js = jsonClass
|
self.js = jsonClass
|
||||||
self.RequestMgr = RequestMgr
|
self.RequestMgr = RequestMgr
|
||||||
|
@ -62,7 +67,7 @@ class SubscriptionManager:
|
||||||
msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
|
msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
|
||||||
msg += "\r\n</MediaContainer>"
|
msg += "\r\n</MediaContainer>"
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def getTimelineXML(self, playerid, ptype):
|
def getTimelineXML(self, playerid, ptype):
|
||||||
if playerid is not None:
|
if playerid is not None:
|
||||||
info = self.getPlayerProperties(playerid)
|
info = self.getPlayerProperties(playerid)
|
||||||
|
@ -95,21 +100,22 @@ class SubscriptionManager:
|
||||||
xbmc.sleep(100)
|
xbmc.sleep(100)
|
||||||
count += 1
|
count += 1
|
||||||
if keyid:
|
if keyid:
|
||||||
self.lastkey = "/library/metadata/%s"%keyid
|
self.lastkey = "/library/metadata/%s" % keyid
|
||||||
self.lastratingkey = keyid
|
self.ratingkey = keyid
|
||||||
ret += ' location="%s"' % (self.mainlocation)
|
ret += ' location="%s"' % self.mainlocation
|
||||||
ret += ' key="%s"' % (self.lastkey)
|
ret += ' key="%s"' % self.lastkey
|
||||||
ret += ' ratingKey="%s"' % (self.lastratingkey)
|
ret += ' ratingKey="%s"' % self.ratingkey
|
||||||
serv = self.getServerByHost(self.server)
|
serv = self.getServerByHost(self.server)
|
||||||
if info.get('playQueueID'):
|
if info.get('playQueueID'):
|
||||||
self.containerKey = "/playQueues/%s" % info.get('playQueueID')
|
self.containerKey = "/playQueues/%s" % info.get('playQueueID')
|
||||||
ret += ' playQueueID="%s"' % info.get('playQueueID')
|
ret += ' playQueueID="%s"' % info.get('playQueueID')
|
||||||
ret += ' playQueueVersion="%s"' % info.get('playQueueVersion')
|
ret += ' playQueueVersion="%s"' % info.get('playQueueVersion')
|
||||||
ret += ' playQueueItemID="%s"' % (info.get('playQueueItemID'))
|
ret += ' playQueueItemID="%s"' % info.get('playQueueItemID')
|
||||||
ret += ' containerKey="%s"' % self.containerKey
|
ret += ' containerKey="%s"' % self.containerKey
|
||||||
|
ret += ' guid="%s"' % info['guid']
|
||||||
elif keyid:
|
elif keyid:
|
||||||
self.containerKey = self.lastkey
|
self.containerKey = self.lastkey
|
||||||
ret += ' containerKey="%s"' % (self.containerKey)
|
ret += ' containerKey="%s"' % self.containerKey
|
||||||
|
|
||||||
ret += ' duration="%s"' % info['duration']
|
ret += ' duration="%s"' % info['duration']
|
||||||
ret += ' seekRange="0-%s"' % info['duration']
|
ret += ' seekRange="0-%s"' % info['duration']
|
||||||
|
@ -118,7 +124,6 @@ class SubscriptionManager:
|
||||||
ret += ' protocol="%s"' % serv.get('protocol', "http")
|
ret += ' protocol="%s"' % serv.get('protocol', "http")
|
||||||
ret += ' address="%s"' % serv.get('server', self.server)
|
ret += ' address="%s"' % serv.get('server', self.server)
|
||||||
ret += ' port="%s"' % serv.get('port', self.port)
|
ret += ' port="%s"' % serv.get('port', self.port)
|
||||||
ret += ' guid="%s"' % info['guid']
|
|
||||||
ret += ' volume="%s"' % info['volume']
|
ret += ' volume="%s"' % info['volume']
|
||||||
ret += ' shuffle="%s"' % info['shuffle']
|
ret += ' shuffle="%s"' % info['shuffle']
|
||||||
ret += ' mute="%s"' % self.mute
|
ret += ' mute="%s"' % self.mute
|
||||||
|
@ -132,13 +137,14 @@ class SubscriptionManager:
|
||||||
|
|
||||||
def updateCommandID(self, uuid, commandID):
|
def updateCommandID(self, uuid, commandID):
|
||||||
if commandID and self.subscribers.get(uuid, False):
|
if commandID and self.subscribers.get(uuid, False):
|
||||||
self.subscribers[uuid].commandID = int(commandID)
|
self.subscribers[uuid].commandID = int(commandID)
|
||||||
|
|
||||||
def notify(self, event = False):
|
def notify(self, event=False):
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
# Don't tell anyone if we don't know a Plex ID and are still playing
|
# Don't tell anyone if we don't know a Plex ID and are still playing
|
||||||
# (e.g. no stop called). Used for e.g. PVR/TV without PKC usage
|
# (e.g. no stop called). Used for e.g. PVR/TV without PKC usage
|
||||||
if not window('Plex_currently_playing_itemid'):
|
if (not window('Plex_currently_playing_itemid')
|
||||||
|
and not self.lastplayers):
|
||||||
return True
|
return True
|
||||||
players = self.js.getPlayers()
|
players = self.js.getPlayers()
|
||||||
# fetch the message, subscribers or not, since the server
|
# fetch the message, subscribers or not, since the server
|
||||||
|
@ -147,35 +153,46 @@ class SubscriptionManager:
|
||||||
if self.subscribers:
|
if self.subscribers:
|
||||||
with threading.RLock():
|
with threading.RLock():
|
||||||
for sub in self.subscribers.values():
|
for sub in self.subscribers.values():
|
||||||
sub.send_update(msg, len(players)==0)
|
sub.send_update(msg, len(players) == 0)
|
||||||
self.notifyServer(players)
|
self.notifyServer(players)
|
||||||
|
self.lastplayers = players
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def notifyServer(self, players):
|
|
||||||
if not players:
|
|
||||||
return True
|
|
||||||
for p in players.values():
|
|
||||||
info = self.playerprops[p.get('playerid')]
|
|
||||||
params = {'state': 'stopped'}
|
|
||||||
params['containerKey'] = (self.containerKey or "/library/metadata/900000")
|
|
||||||
if info.get('playQueueID'):
|
|
||||||
params['containerKey'] = '/playQueues/' + info['playQueueID']
|
|
||||||
params['playQueueVersion'] = info['playQueueVersion']
|
|
||||||
params['playQueueItemID'] = info['playQueueItemID']
|
|
||||||
params['key'] = (self.lastkey or "/library/metadata/900000")
|
|
||||||
params['ratingKey'] = (self.lastratingkey or "900000")
|
|
||||||
params['state'] = info['state']
|
|
||||||
params['time'] = info['time']
|
|
||||||
params['duration'] = info['duration']
|
|
||||||
|
|
||||||
serv = self.getServerByHost(self.server)
|
def notifyServer(self, players):
|
||||||
url = serv.get('protocol', 'http') + '://' \
|
for typus, p in players.iteritems():
|
||||||
+ serv.get('server', 'localhost') + ':' \
|
info = self.playerprops[p.get('playerid')]
|
||||||
+ serv.get('port', '32400') + "/:/timeline"
|
self._sendNotification(info)
|
||||||
self.doUtils(url, type="GET", parameters=params)
|
self.lastinfo[typus] = info
|
||||||
# requests.getwithparams(serv.get('server', 'localhost'), serv.get('port', 32400), "/:/timeline", params, getPlexHeaders(), serv.get('protocol', 'http'))
|
# Cross the one of the list
|
||||||
self.logMsg("sent server notification with state = %s"
|
try:
|
||||||
% params['state'], 2)
|
del self.lastplayers[typus]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
# Process the players we have left (to signal a stop)
|
||||||
|
for typus, p in self.lastplayers.iteritems():
|
||||||
|
self.lastinfo[typus]['state'] = 'stopped'
|
||||||
|
self._sendNotification(self.lastinfo[typus])
|
||||||
|
|
||||||
|
def _sendNotification(self, info):
|
||||||
|
params = {
|
||||||
|
'containerKey': self.containerKey or "/library/metadata/900000",
|
||||||
|
'key': self.lastkey or "/library/metadata/900000",
|
||||||
|
'ratingKey': self.ratingkey or "900000",
|
||||||
|
'state': info['state'],
|
||||||
|
'time': info['time'],
|
||||||
|
'duration': info['duration']
|
||||||
|
}
|
||||||
|
if info.get('playQueueID'):
|
||||||
|
params['containerKey'] = '/playQueues/%s' % info['playQueueID']
|
||||||
|
params['playQueueVersion'] = info['playQueueVersion']
|
||||||
|
params['playQueueItemID'] = info['playQueueItemID']
|
||||||
|
serv = self.getServerByHost(self.server)
|
||||||
|
url = '%s://%s:%s/:/timeline' % (serv.get('protocol', 'http'),
|
||||||
|
serv.get('server', 'localhost'),
|
||||||
|
serv.get('port', '32400'))
|
||||||
|
self.doUtils(url, parameters=params)
|
||||||
|
self.logMsg("Sent server notification with parameters: %s to %s"
|
||||||
|
% (params, url), 2)
|
||||||
|
|
||||||
def controllable(self):
|
def controllable(self):
|
||||||
return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause"
|
return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause"
|
||||||
|
@ -205,30 +222,50 @@ class SubscriptionManager:
|
||||||
if sub.age > 30:
|
if sub.age > 30:
|
||||||
sub.cleanup()
|
sub.cleanup()
|
||||||
del self.subscribers[sub.uuid]
|
del self.subscribers[sub.uuid]
|
||||||
|
|
||||||
def getPlayerProperties(self, playerid):
|
def getPlayerProperties(self, playerid):
|
||||||
info = {}
|
|
||||||
try:
|
try:
|
||||||
# get info from the player
|
# get info from the player
|
||||||
props = self.js.jsonrpc("Player.GetProperties", {"playerid": playerid, "properties": ["time", "totaltime", "speed", "shuffled", "repeat"]})
|
props = self.js.jsonrpc(
|
||||||
self.logMsg(self.js.jsonrpc("Player.GetItem", {"playerid": playerid, "properties": ["file", "showlink", "episode", "season"]}), 2)
|
"Player.GetProperties",
|
||||||
info['time'] = timeToMillis(props['time'])
|
{"playerid": playerid,
|
||||||
info['duration'] = timeToMillis(props['totaltime'])
|
"properties": ["time",
|
||||||
info['state'] = ("paused", "playing")[int(props['speed'])]
|
"totaltime",
|
||||||
info['shuffle'] = ("0","1")[props.get('shuffled', False)]
|
"speed",
|
||||||
info['repeat'] = pf.getPlexRepeat(props.get('repeat'))
|
"shuffled",
|
||||||
# New PMS playQueue attributes
|
"repeat"]})
|
||||||
cf = self.xbmcplayer.getPlayingFile()
|
|
||||||
info['playQueueID'] = window('playQueueID')
|
|
||||||
info['playQueueVersion'] = window('playQueueVersion')
|
|
||||||
info['playQueueItemID'] = window('plex_%s.playQueueItemID' % cf)
|
|
||||||
info['guid'] = window('plex_%s.guid' % cf)
|
|
||||||
|
|
||||||
|
info = {
|
||||||
|
'time': timeToMillis(props['time']),
|
||||||
|
'duration': timeToMillis(props['totaltime']),
|
||||||
|
'state': ("paused", "playing")[int(props['speed'])],
|
||||||
|
'shuffle': ("0", "1")[props.get('shuffled', False)],
|
||||||
|
'repeat': pf.getPlexRepeat(props.get('repeat')),
|
||||||
|
}
|
||||||
|
if self.playlist is not None:
|
||||||
|
if self.playlist.QueueId() is not None:
|
||||||
|
info['playQueueID'] = self.playlist.QueueId()
|
||||||
|
info['playQueueVersion'] = self.playlist.PlayQueueVersion()
|
||||||
|
info['guid'] = self.playlist.Guid()
|
||||||
|
# Get the playlist position
|
||||||
|
pos = self.js.jsonrpc(
|
||||||
|
"Player.GetProperties",
|
||||||
|
{"playerid": playerid,
|
||||||
|
"properties": ["position"]})
|
||||||
|
info['playQueueItemID'] = \
|
||||||
|
self.playlist.getQueueIdFromPosition(pos['position'])
|
||||||
except:
|
except:
|
||||||
info['time'] = 0
|
import traceback
|
||||||
info['duration'] = 0
|
self.logMsg("Traceback:\n%s"
|
||||||
info['state'] = "stopped"
|
% traceback.format_exc(), -1)
|
||||||
info['shuffle'] = False
|
info = {
|
||||||
|
'time': 0,
|
||||||
|
'duration': 0,
|
||||||
|
'state': 'stopped',
|
||||||
|
'shuffle': False,
|
||||||
|
'repeat': 0
|
||||||
|
}
|
||||||
|
|
||||||
# get the volume from the application
|
# get the volume from the application
|
||||||
info['volume'] = self.volume
|
info['volume'] = self.volume
|
||||||
info['mute'] = self.mute
|
info['mute'] = self.mute
|
||||||
|
@ -283,6 +320,6 @@ class Subscriber:
|
||||||
"""
|
"""
|
||||||
response = self.doUtils(url,
|
response = self.doUtils(url,
|
||||||
postBody=msg,
|
postBody=msg,
|
||||||
type="POST")
|
action_type="POST")
|
||||||
if response in [False, None, 401]:
|
if response in [False, None, 401]:
|
||||||
self.subMgr.removeSubscriber(self.uuid)
|
self.subMgr.removeSubscriber(self.uuid)
|
||||||
|
|
|
@ -31,8 +31,7 @@ class Read_EmbyServer():
|
||||||
# This will return the full item
|
# This will return the full item
|
||||||
item = {}
|
item = {}
|
||||||
|
|
||||||
url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
|
result = self.doUtils("{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid)
|
||||||
result = self.doUtils(url)
|
|
||||||
if result:
|
if result:
|
||||||
item = result
|
item = result
|
||||||
|
|
||||||
|
@ -45,13 +44,12 @@ class Read_EmbyServer():
|
||||||
itemlists = self.split_list(itemlist, 50)
|
itemlists = self.split_list(itemlist, 50)
|
||||||
for itemlist in itemlists:
|
for itemlist in itemlists:
|
||||||
# Will return basic information
|
# Will return basic information
|
||||||
url = "{server}/emby/Users/{UserId}/Items?&format=json"
|
|
||||||
params = {
|
params = {
|
||||||
|
|
||||||
'Ids': ",".join(itemlist),
|
'Ids': ",".join(itemlist),
|
||||||
'Fields': "Etag"
|
'Fields': "Etag"
|
||||||
}
|
}
|
||||||
result = self.doUtils(url, parameters=params)
|
result = self.doUtils("{server}/emby/Users/{UserId}/Items?&format=json", parameters=params)
|
||||||
if result:
|
if result:
|
||||||
items.extend(result['Items'])
|
items.extend(result['Items'])
|
||||||
|
|
||||||
|
@ -64,7 +62,6 @@ class Read_EmbyServer():
|
||||||
itemlists = self.split_list(itemlist, 50)
|
itemlists = self.split_list(itemlist, 50)
|
||||||
for itemlist in itemlists:
|
for itemlist in itemlists:
|
||||||
|
|
||||||
url = "{server}/emby/Users/{UserId}/Items?format=json"
|
|
||||||
params = {
|
params = {
|
||||||
|
|
||||||
"Ids": ",".join(itemlist),
|
"Ids": ",".join(itemlist),
|
||||||
|
@ -75,25 +72,22 @@ class Read_EmbyServer():
|
||||||
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
||||||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||||
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,"
|
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,"
|
||||||
"MediaSources"
|
"MediaSources,VoteCount"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
result = self.doUtils(url, parameters=params)
|
result = self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
|
||||||
if result:
|
if result:
|
||||||
items.extend(result['Items'])
|
items.extend(result['Items'])
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def getView_embyId(self, itemid):
|
def getView_plexid(self, itemid):
|
||||||
# Returns ancestors using embyId
|
# Returns ancestors using plexid
|
||||||
viewId = None
|
viewId = None
|
||||||
url = "{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid
|
|
||||||
result = self.doUtils(url)
|
|
||||||
|
|
||||||
for view in result:
|
for view in self.doUtils("{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid):
|
||||||
|
|
||||||
viewtype = view['Type']
|
if view['Type'] == "CollectionFolder":
|
||||||
if viewtype == "CollectionFolder":
|
|
||||||
# Found view
|
# Found view
|
||||||
viewId = view['Id']
|
viewId = view['Id']
|
||||||
|
|
||||||
|
@ -120,8 +114,6 @@ class Read_EmbyServer():
|
||||||
return [viewName, viewId, mediatype]
|
return [viewName, viewId, mediatype]
|
||||||
|
|
||||||
def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True, limit=None, sortorder="Ascending", filter=""):
|
def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True, limit=None, sortorder="Ascending", filter=""):
|
||||||
doUtils = self.doUtils
|
|
||||||
url = "{server}/emby/Users/{UserId}/Items?format=json"
|
|
||||||
params = {
|
params = {
|
||||||
|
|
||||||
'ParentId': parentid,
|
'ParentId': parentid,
|
||||||
|
@ -140,11 +132,9 @@ class Read_EmbyServer():
|
||||||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||||
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
|
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
|
||||||
}
|
}
|
||||||
return doUtils(url, parameters=params)
|
return self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
|
||||||
|
|
||||||
def getTvChannels(self):
|
def getTvChannels(self):
|
||||||
doUtils = self.doUtils
|
|
||||||
url = "{server}/emby/LiveTv/Channels/?userid={UserId}&format=json"
|
|
||||||
params = {
|
params = {
|
||||||
|
|
||||||
'EnableImages': True,
|
'EnableImages': True,
|
||||||
|
@ -154,11 +144,9 @@ class Read_EmbyServer():
|
||||||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||||
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
|
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
|
||||||
}
|
}
|
||||||
return doUtils(url, parameters=params)
|
return self.doUtils("{server}/emby/LiveTv/Channels/?userid={UserId}&format=json", parameters=params)
|
||||||
|
|
||||||
def getTvRecordings(self, groupid):
|
def getTvRecordings(self, groupid):
|
||||||
doUtils = self.doUtils
|
|
||||||
url = "{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json"
|
|
||||||
if groupid == "root": groupid = ""
|
if groupid == "root": groupid = ""
|
||||||
params = {
|
params = {
|
||||||
|
|
||||||
|
@ -170,13 +158,10 @@ class Read_EmbyServer():
|
||||||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||||
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
|
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
|
||||||
}
|
}
|
||||||
return doUtils(url, parameters=params)
|
return self.doUtils("{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json", parameters=params)
|
||||||
|
|
||||||
def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None):
|
def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None):
|
||||||
|
|
||||||
log = self.logMsg
|
|
||||||
|
|
||||||
doUtils = self.doUtils
|
|
||||||
items = {
|
items = {
|
||||||
|
|
||||||
'Items': [],
|
'Items': [],
|
||||||
|
@ -195,13 +180,13 @@ class Read_EmbyServer():
|
||||||
'Recursive': True,
|
'Recursive': True,
|
||||||
'Limit': 1
|
'Limit': 1
|
||||||
}
|
}
|
||||||
result = doUtils(url, parameters=params)
|
result = self.doUtils(url, parameters=params)
|
||||||
try:
|
try:
|
||||||
total = result['TotalRecordCount']
|
total = result['TotalRecordCount']
|
||||||
items['TotalRecordCount'] = total
|
items['TotalRecordCount'] = total
|
||||||
|
|
||||||
except TypeError: # Failed to retrieve
|
except TypeError: # Failed to retrieve
|
||||||
log("%s:%s Failed to retrieve the server response." % (url, params), 2)
|
self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
index = 0
|
index = 0
|
||||||
|
@ -234,36 +219,36 @@ class Read_EmbyServer():
|
||||||
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
||||||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||||
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,"
|
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,"
|
||||||
"MediaSources"
|
"MediaSources,VoteCount"
|
||||||
)
|
)
|
||||||
result = doUtils(url, parameters=params)
|
result = self.doUtils(url, parameters=params)
|
||||||
try:
|
try:
|
||||||
items['Items'].extend(result['Items'])
|
items['Items'].extend(result['Items'])
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# Something happened to the connection
|
# Something happened to the connection
|
||||||
if not throttled:
|
if not throttled:
|
||||||
throttled = True
|
throttled = True
|
||||||
log("Throttle activated.", 1)
|
self.logMsg("Throttle activated.", 1)
|
||||||
|
|
||||||
if jump == highestjump:
|
if jump == highestjump:
|
||||||
# We already tried with the highestjump, but it failed. Reset value.
|
# We already tried with the highestjump, but it failed. Reset value.
|
||||||
log("Reset highest value.", 1)
|
self.logMsg("Reset highest value.", 1)
|
||||||
highestjump = 0
|
highestjump = 0
|
||||||
|
|
||||||
# Lower the number by half
|
# Lower the number by half
|
||||||
if highestjump:
|
if highestjump:
|
||||||
throttled = False
|
throttled = False
|
||||||
jump = highestjump
|
jump = highestjump
|
||||||
log("Throttle deactivated.", 1)
|
self.logMsg("Throttle deactivated.", 1)
|
||||||
else:
|
else:
|
||||||
jump = int(jump/4)
|
jump = int(jump/4)
|
||||||
log("Set jump limit to recover: %s" % jump, 2)
|
self.logMsg("Set jump limit to recover: %s" % jump, 2)
|
||||||
|
|
||||||
retry = 0
|
retry = 0
|
||||||
while utils.window('emby_online') != "true":
|
while utils.window('plex_online') != "true":
|
||||||
# Wait server to come back online
|
# Wait server to come back online
|
||||||
if retry == 5:
|
if retry == 5:
|
||||||
log("Unable to reconnect to server. Abort process.", 1)
|
self.logMsg("Unable to reconnect to server. Abort process.", 1)
|
||||||
return items
|
return items
|
||||||
|
|
||||||
retry += 1
|
retry += 1
|
||||||
|
@ -291,12 +276,11 @@ class Read_EmbyServer():
|
||||||
increment = 10
|
increment = 10
|
||||||
|
|
||||||
jump += increment
|
jump += increment
|
||||||
log("Increase jump limit to: %s" % jump, 1)
|
self.logMsg("Increase jump limit to: %s" % jump, 1)
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def getViews(self, mediatype="", root=False, sortedlist=False):
|
def getViews(self, mediatype="", root=False, sortedlist=False):
|
||||||
# Build a list of user views
|
# Build a list of user views
|
||||||
doUtils = self.doUtils
|
|
||||||
views = []
|
views = []
|
||||||
mediatype = mediatype.lower()
|
mediatype = mediatype.lower()
|
||||||
|
|
||||||
|
@ -305,7 +289,7 @@ class Read_EmbyServer():
|
||||||
else: # Views ungrouped
|
else: # Views ungrouped
|
||||||
url = "{server}/emby/Users/{UserId}/Items?Sortby=SortName&format=json"
|
url = "{server}/emby/Users/{UserId}/Items?Sortby=SortName&format=json"
|
||||||
|
|
||||||
result = doUtils(url)
|
result = self.doUtils(url)
|
||||||
try:
|
try:
|
||||||
items = result['Items']
|
items = result['Items']
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -313,11 +297,8 @@ class Read_EmbyServer():
|
||||||
else:
|
else:
|
||||||
for item in items:
|
for item in items:
|
||||||
|
|
||||||
name = item['Name']
|
item['Name'] = item['Name']
|
||||||
itemId = item['Id']
|
if item['Type'] == "Channel":
|
||||||
viewtype = item['Type']
|
|
||||||
|
|
||||||
if viewtype == "Channel":
|
|
||||||
# Filter view types
|
# Filter view types
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -328,20 +309,20 @@ class Read_EmbyServer():
|
||||||
# Assumed missing is mixed then.
|
# Assumed missing is mixed then.
|
||||||
'''if itemtype is None:
|
'''if itemtype is None:
|
||||||
url = "{server}/emby/Library/MediaFolders?format=json"
|
url = "{server}/emby/Library/MediaFolders?format=json"
|
||||||
result = doUtils(url)
|
result = self.doUtils(url)
|
||||||
|
|
||||||
for folder in result['Items']:
|
for folder in result['Items']:
|
||||||
if itemId == folder['Id']:
|
if item['Id'] == folder['Id']:
|
||||||
itemtype = folder.get('CollectionType', "mixed")'''
|
itemtype = folder.get('CollectionType', "mixed")'''
|
||||||
|
|
||||||
if name not in ('Collections', 'Trailers'):
|
if item['Name'] not in ('Collections', 'Trailers'):
|
||||||
|
|
||||||
if sortedlist:
|
if sortedlist:
|
||||||
views.append({
|
views.append({
|
||||||
|
|
||||||
'name': name,
|
'name': item['Name'],
|
||||||
'type': itemtype,
|
'type': itemtype,
|
||||||
'id': itemId
|
'id': item['Id']
|
||||||
})
|
})
|
||||||
|
|
||||||
elif (itemtype == mediatype or
|
elif (itemtype == mediatype or
|
||||||
|
@ -349,9 +330,9 @@ class Read_EmbyServer():
|
||||||
|
|
||||||
views.append({
|
views.append({
|
||||||
|
|
||||||
'name': name,
|
'name': item['Name'],
|
||||||
'type': itemtype,
|
'type': itemtype,
|
||||||
'id': itemId
|
'id': item['Id']
|
||||||
})
|
})
|
||||||
|
|
||||||
return views
|
return views
|
||||||
|
@ -359,8 +340,6 @@ class Read_EmbyServer():
|
||||||
def verifyView(self, parentid, itemid):
|
def verifyView(self, parentid, itemid):
|
||||||
|
|
||||||
belongs = False
|
belongs = False
|
||||||
|
|
||||||
url = "{server}/emby/Users/{UserId}/Items?format=json"
|
|
||||||
params = {
|
params = {
|
||||||
|
|
||||||
'ParentId': parentid,
|
'ParentId': parentid,
|
||||||
|
@ -370,7 +349,7 @@ class Read_EmbyServer():
|
||||||
'Recursive': True,
|
'Recursive': True,
|
||||||
'Ids': itemid
|
'Ids': itemid
|
||||||
}
|
}
|
||||||
result = self.doUtils(url, parameters=params)
|
result = self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
|
||||||
try:
|
try:
|
||||||
total = result['TotalRecordCount']
|
total = result['TotalRecordCount']
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -383,40 +362,23 @@ class Read_EmbyServer():
|
||||||
return belongs
|
return belongs
|
||||||
|
|
||||||
def getMovies(self, parentId, basic=False, dialog=None):
|
def getMovies(self, parentId, basic=False, dialog=None):
|
||||||
|
return self.getSection(parentId, "Movie", basic=basic, dialog=dialog)
|
||||||
items = self.getSection(parentId, "Movie", basic=basic, dialog=dialog)
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getBoxset(self, dialog=None):
|
def getBoxset(self, dialog=None):
|
||||||
|
return self.getSection(None, "BoxSet", dialog=dialog)
|
||||||
items = self.getSection(None, "BoxSet", dialog=dialog)
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getMovies_byBoxset(self, boxsetid):
|
def getMovies_byBoxset(self, boxsetid):
|
||||||
|
return self.getSection(boxsetid, "Movie")
|
||||||
items = self.getSection(boxsetid, "Movie")
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getMusicVideos(self, parentId, basic=False, dialog=None):
|
def getMusicVideos(self, parentId, basic=False, dialog=None):
|
||||||
|
return self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog)
|
||||||
items = self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog)
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getHomeVideos(self, parentId):
|
def getHomeVideos(self, parentId):
|
||||||
|
|
||||||
items = self.getSection(parentId, "Video")
|
return self.getSection(parentId, "Video")
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getShows(self, parentId, basic=False, dialog=None):
|
def getShows(self, parentId, basic=False, dialog=None):
|
||||||
|
return self.getSection(parentId, "Series", basic=basic, dialog=dialog)
|
||||||
items = self.getSection(parentId, "Series", basic=basic, dialog=dialog)
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getSeasons(self, showId):
|
def getSeasons(self, showId):
|
||||||
|
|
||||||
|
@ -426,13 +388,12 @@ class Read_EmbyServer():
|
||||||
'TotalRecordCount': 0
|
'TotalRecordCount': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
url = "{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId
|
|
||||||
params = {
|
params = {
|
||||||
|
|
||||||
'IsVirtualUnaired': False,
|
'IsVirtualUnaired': False,
|
||||||
'Fields': "Etag"
|
'Fields': "Etag"
|
||||||
}
|
}
|
||||||
result = self.doUtils(url, parameters=params)
|
result = self.doUtils("{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId, parameters=params)
|
||||||
if result:
|
if result:
|
||||||
items = result
|
items = result
|
||||||
|
|
||||||
|
@ -440,25 +401,19 @@ class Read_EmbyServer():
|
||||||
|
|
||||||
def getEpisodes(self, parentId, basic=False, dialog=None):
|
def getEpisodes(self, parentId, basic=False, dialog=None):
|
||||||
|
|
||||||
items = self.getSection(parentId, "Episode", basic=basic, dialog=dialog)
|
return self.getSection(parentId, "Episode", basic=basic, dialog=dialog)
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getEpisodesbyShow(self, showId):
|
def getEpisodesbyShow(self, showId):
|
||||||
|
|
||||||
items = self.getSection(showId, "Episode")
|
return self.getSection(showId, "Episode")
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getEpisodesbySeason(self, seasonId):
|
def getEpisodesbySeason(self, seasonId):
|
||||||
|
|
||||||
items = self.getSection(seasonId, "Episode")
|
return self.getSection(seasonId, "Episode")
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getArtists(self, dialog=None):
|
def getArtists(self, dialog=None):
|
||||||
|
|
||||||
doUtils = self.doUtils
|
|
||||||
items = {
|
items = {
|
||||||
|
|
||||||
'Items': [],
|
'Items': [],
|
||||||
|
@ -472,7 +427,7 @@ class Read_EmbyServer():
|
||||||
'Recursive': True,
|
'Recursive': True,
|
||||||
'Limit': 1
|
'Limit': 1
|
||||||
}
|
}
|
||||||
result = doUtils(url, parameters=params)
|
result = self.doUtils(url, parameters=params)
|
||||||
try:
|
try:
|
||||||
total = result['TotalRecordCount']
|
total = result['TotalRecordCount']
|
||||||
items['TotalRecordCount'] = total
|
items['TotalRecordCount'] = total
|
||||||
|
@ -502,7 +457,7 @@ class Read_EmbyServer():
|
||||||
"AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview"
|
"AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
result = doUtils(url, parameters=params)
|
result = self.doUtils(url, parameters=params)
|
||||||
items['Items'].extend(result['Items'])
|
items['Items'].extend(result['Items'])
|
||||||
|
|
||||||
index += jump
|
index += jump
|
||||||
|
@ -512,28 +467,17 @@ class Read_EmbyServer():
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def getAlbums(self, basic=False, dialog=None):
|
def getAlbums(self, basic=False, dialog=None):
|
||||||
|
return self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog)
|
||||||
items = self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog)
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getAlbumsbyArtist(self, artistId):
|
def getAlbumsbyArtist(self, artistId):
|
||||||
|
return self.getSection(artistId, "MusicAlbum", sortby="DateCreated")
|
||||||
items = self.getSection(artistId, "MusicAlbum", sortby="DateCreated")
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getSongs(self, basic=False, dialog=None):
|
def getSongs(self, basic=False, dialog=None):
|
||||||
|
return self.getSection(None, "Audio", basic=basic, dialog=dialog)
|
||||||
items = self.getSection(None, "Audio", basic=basic, dialog=dialog)
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getSongsbyAlbum(self, albumId):
|
def getSongsbyAlbum(self, albumId):
|
||||||
|
return self.getSection(albumId, "Audio")
|
||||||
|
|
||||||
items = self.getSection(albumId, "Audio")
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getAdditionalParts(self, itemId):
|
def getAdditionalParts(self, itemId):
|
||||||
|
|
||||||
|
@ -543,8 +487,7 @@ class Read_EmbyServer():
|
||||||
'TotalRecordCount': 0
|
'TotalRecordCount': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
url = "{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId
|
result = self.doUtils("{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId)
|
||||||
result = self.doUtils(url)
|
|
||||||
if result:
|
if result:
|
||||||
items = result
|
items = result
|
||||||
|
|
||||||
|
@ -566,25 +509,21 @@ class Read_EmbyServer():
|
||||||
|
|
||||||
def updateUserRating(self, itemid, like=None, favourite=None, deletelike=False):
|
def updateUserRating(self, itemid, like=None, favourite=None, deletelike=False):
|
||||||
# Updates the user rating to Emby
|
# Updates the user rating to Emby
|
||||||
doUtils = self.doUtils
|
|
||||||
|
|
||||||
if favourite:
|
if favourite:
|
||||||
url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid
|
self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="POST")
|
||||||
doUtils(url, type="POST")
|
|
||||||
elif favourite == False:
|
elif favourite == False:
|
||||||
url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid
|
self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="DELETE")
|
||||||
doUtils(url, type="DELETE")
|
|
||||||
|
|
||||||
if not deletelike and like:
|
if not deletelike and like:
|
||||||
url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=true&format=json" % itemid
|
self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=true&format=json" % itemid, action_type="POST")
|
||||||
doUtils(url, type="POST")
|
elif not deletelike and like is False:
|
||||||
elif not deletelike and like == False:
|
self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid, action_type="POST")
|
||||||
url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid
|
|
||||||
doUtil(url, type="POST")
|
|
||||||
elif deletelike:
|
elif deletelike:
|
||||||
url = "{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid
|
self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid, action_type="DELETE")
|
||||||
doUtils(url, type="DELETE")
|
else:
|
||||||
|
self.logMsg("Error processing user rating.", 1)
|
||||||
|
|
||||||
self.logMsg("Update user rating to emby for itemid: %s "
|
self.logMsg("Update user rating to emby for itemid: %s "
|
||||||
"| like: %s | favourite: %s | deletelike: %s"
|
"| like: %s | favourite: %s | deletelike: %s"
|
||||||
% (itemid, like, favourite, deletelike), 1)
|
% (itemid, like, favourite, deletelike), 1)
|
||||||
|
|
|
@ -114,8 +114,6 @@ class UserClient(threading.Thread):
|
||||||
# url = "{server}/emby/System/Configuration?format=json"
|
# url = "{server}/emby/System/Configuration?format=json"
|
||||||
# result = doUtils.downloadUrl(url)
|
# result = doUtils.downloadUrl(url)
|
||||||
|
|
||||||
# utils.settings('markPlayed', value=str(result['MaxResumePct']))
|
|
||||||
|
|
||||||
def hasAccess(self):
|
def hasAccess(self):
|
||||||
# Plex: always return True for now
|
# Plex: always return True for now
|
||||||
return True
|
return True
|
||||||
|
@ -126,19 +124,19 @@ class UserClient(threading.Thread):
|
||||||
url = "{server}/emby/Users?format=json"
|
url = "{server}/emby/Users?format=json"
|
||||||
result = self.doUtils.downloadUrl(url)
|
result = self.doUtils.downloadUrl(url)
|
||||||
|
|
||||||
if result == False:
|
if result is False:
|
||||||
# Access is restricted, set in downloadutils.py via exception
|
# Access is restricted, set in downloadutils.py via exception
|
||||||
log("Access is restricted.", 1)
|
log("Access is restricted.", 1)
|
||||||
self.HasAccess = False
|
self.HasAccess = False
|
||||||
|
|
||||||
elif window('emby_online') != "true":
|
elif window('plex_online') != "true":
|
||||||
# Server connection failed
|
# Server connection failed
|
||||||
pass
|
pass
|
||||||
|
|
||||||
elif window('emby_serverStatus') == "restricted":
|
elif window('plex_serverStatus') == "restricted":
|
||||||
log("Access is granted.", 1)
|
log("Access is granted.", 1)
|
||||||
self.HasAccess = True
|
self.HasAccess = True
|
||||||
window('emby_serverStatus', clear=True)
|
window('plex_serverStatus', clear=True)
|
||||||
xbmcgui.Dialog().notification(self.addonName,
|
xbmcgui.Dialog().notification(self.addonName,
|
||||||
utils.language(33007))
|
utils.language(33007))
|
||||||
|
|
||||||
|
@ -239,7 +237,7 @@ class UserClient(threading.Thread):
|
||||||
# Give attempts at entering password / selecting user
|
# Give attempts at entering password / selecting user
|
||||||
if self.retry >= 2:
|
if self.retry >= 2:
|
||||||
log("Too many retries to login.", -1)
|
log("Too many retries to login.", -1)
|
||||||
window('emby_serverStatus', value="Stop")
|
window('plex_serverStatus', value="Stop")
|
||||||
dialog.ok(lang(33001),
|
dialog.ok(lang(33001),
|
||||||
lang(39023))
|
lang(39023))
|
||||||
xbmc.executebuiltin(
|
xbmc.executebuiltin(
|
||||||
|
@ -247,8 +245,8 @@ class UserClient(threading.Thread):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Get /profile/addon_data
|
# Get /profile/addon_data
|
||||||
addondir = xbmc.translatePath(
|
addondir = utils.tryDecode(xbmc.translatePath(
|
||||||
self.addon.getAddonInfo('profile')).decode('utf-8')
|
self.addon.getAddonInfo('profile')))
|
||||||
hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
|
hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
|
||||||
|
|
||||||
# If there's no settings.xml
|
# If there's no settings.xml
|
||||||
|
@ -358,7 +356,7 @@ class UserClient(threading.Thread):
|
||||||
break
|
break
|
||||||
xbmc.sleep(1000)
|
xbmc.sleep(1000)
|
||||||
|
|
||||||
status = window('emby_serverStatus')
|
status = window('plex_serverStatus')
|
||||||
|
|
||||||
if status == "Stop":
|
if status == "Stop":
|
||||||
xbmc.sleep(500)
|
xbmc.sleep(500)
|
||||||
|
@ -371,7 +369,7 @@ class UserClient(threading.Thread):
|
||||||
|
|
||||||
elif status == "401":
|
elif status == "401":
|
||||||
# Unauthorized access, revoke token
|
# Unauthorized access, revoke token
|
||||||
window('emby_serverStatus', value="Auth")
|
window('plex_serverStatus', value="Auth")
|
||||||
self.resetClient()
|
self.resetClient()
|
||||||
xbmc.sleep(2000)
|
xbmc.sleep(2000)
|
||||||
|
|
||||||
|
@ -389,7 +387,7 @@ class UserClient(threading.Thread):
|
||||||
log("Current accessToken: xxxx", 1)
|
log("Current accessToken: xxxx", 1)
|
||||||
self.retry = 0
|
self.retry = 0
|
||||||
window('suspend_LibraryThread', clear=True)
|
window('suspend_LibraryThread', clear=True)
|
||||||
window('emby_serverStatus', clear=True)
|
window('plex_serverStatus', clear=True)
|
||||||
|
|
||||||
if not self.auth and (self.currUser is None):
|
if not self.auth and (self.currUser is None):
|
||||||
# Loop if no server found
|
# Loop if no server found
|
||||||
|
|
|
@ -16,6 +16,7 @@ from functools import wraps
|
||||||
from calendar import timegm
|
from calendar import timegm
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
|
@ -98,7 +99,7 @@ def IfExists(path):
|
||||||
|
|
||||||
Returns True if path exists, else false
|
Returns True if path exists, else false
|
||||||
"""
|
"""
|
||||||
dummyfile = os.path.join(path, 'dummyfile.txt').encode('utf-8')
|
dummyfile = tryEncode(os.path.join(path, 'dummyfile.txt'))
|
||||||
try:
|
try:
|
||||||
etree.ElementTree(etree.Element('test')).write(dummyfile)
|
etree.ElementTree(etree.Element('test')).write(dummyfile)
|
||||||
except:
|
except:
|
||||||
|
@ -111,6 +112,46 @@ def IfExists(path):
|
||||||
return answer
|
return answer
|
||||||
|
|
||||||
|
|
||||||
|
def forEveryMethod(decorator):
|
||||||
|
"""
|
||||||
|
Wrapper for classes to add the decorator "decorator" to all methods of the
|
||||||
|
class
|
||||||
|
"""
|
||||||
|
def decorate(cls):
|
||||||
|
for attr in cls.__dict__: # there's propably a better way to do this
|
||||||
|
if callable(getattr(cls, attr)):
|
||||||
|
setattr(cls, attr, decorator(getattr(cls, attr)))
|
||||||
|
return cls
|
||||||
|
return decorate
|
||||||
|
|
||||||
|
|
||||||
|
def CatchExceptions(warnuser=False):
|
||||||
|
"""
|
||||||
|
Decorator for methods to catch exceptions and log them. Useful for e.g.
|
||||||
|
librarysync threads using itemtypes.py, because otherwise we would not
|
||||||
|
get informed of crashes
|
||||||
|
|
||||||
|
warnuser=True: sets the window flag 'plex_scancrashed' to true
|
||||||
|
which will trigger a Kodi infobox to inform user
|
||||||
|
"""
|
||||||
|
def decorate(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
logMsg(addonName, '%s has crashed' % func.__name__, -1)
|
||||||
|
logMsg(addonName, e, -1)
|
||||||
|
import traceback
|
||||||
|
logMsg(addonName, "Traceback:\n%s"
|
||||||
|
% traceback.format_exc(), -1)
|
||||||
|
if warnuser:
|
||||||
|
window('plex_scancrashed', value='true')
|
||||||
|
return
|
||||||
|
return wrapper
|
||||||
|
return decorate
|
||||||
|
|
||||||
|
|
||||||
def LogTime(func):
|
def LogTime(func):
|
||||||
"""
|
"""
|
||||||
Decorator for functions and methods to log the time it took to run the code
|
Decorator for functions and methods to log the time it took to run the code
|
||||||
|
@ -254,7 +295,7 @@ def getUnixTimestamp(secondsIntoTheFuture=None):
|
||||||
def logMsg(title, msg, level=1):
|
def logMsg(title, msg, level=1):
|
||||||
# Get the logLevel set in UserClient
|
# Get the logLevel set in UserClient
|
||||||
try:
|
try:
|
||||||
logLevel = int(window('emby_logLevel'))
|
logLevel = int(window('plex_logLevel'))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logLevel = 0
|
logLevel = 0
|
||||||
kodiLevel = {
|
kodiLevel = {
|
||||||
|
@ -272,7 +313,7 @@ def logMsg(title, msg, level=1):
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
try:
|
try:
|
||||||
xbmc.log("%s -> %s : %s" % (
|
xbmc.log("%s -> %s : %s" % (
|
||||||
title, func.co_name, msg.encode('utf-8')),
|
title, func.co_name, tryEncode(msg)),
|
||||||
level=kodiLevel[level])
|
level=kodiLevel[level])
|
||||||
except:
|
except:
|
||||||
xbmc.log("%s -> %s : %s" % (
|
xbmc.log("%s -> %s : %s" % (
|
||||||
|
@ -283,7 +324,7 @@ def logMsg(title, msg, level=1):
|
||||||
xbmc.log("%s -> %s" % (title, msg), level=kodiLevel[level])
|
xbmc.log("%s -> %s" % (title, msg), level=kodiLevel[level])
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
try:
|
try:
|
||||||
xbmc.log("%s -> %s" % (title, msg.encode('utf-8')),
|
xbmc.log("%s -> %s" % (title, tryEncode(msg)),
|
||||||
level=kodiLevel[level])
|
level=kodiLevel[level])
|
||||||
except:
|
except:
|
||||||
xbmc.log("%s -> %s " % (title, 'COULDNT LOG'),
|
xbmc.log("%s -> %s " % (title, 'COULDNT LOG'),
|
||||||
|
@ -299,18 +340,14 @@ def window(property, value=None, clear=False, windowid=10000):
|
||||||
Property needs to be string; value may be string or unicode
|
Property needs to be string; value may be string or unicode
|
||||||
"""
|
"""
|
||||||
WINDOW = xbmcgui.Window(windowid)
|
WINDOW = xbmcgui.Window(windowid)
|
||||||
|
|
||||||
#setproperty accepts both string and unicode but utf-8 strings are adviced by kodi devs because some unicode can give issues
|
#setproperty accepts both string and unicode but utf-8 strings are adviced by kodi devs because some unicode can give issues
|
||||||
'''if isinstance(property, unicode):
|
|
||||||
property = property.encode("utf-8")
|
|
||||||
if isinstance(value, unicode):
|
|
||||||
value = value.encode("utf-8")'''
|
|
||||||
if clear:
|
if clear:
|
||||||
WINDOW.clearProperty(property)
|
WINDOW.clearProperty(property)
|
||||||
elif value is not None:
|
elif value is not None:
|
||||||
WINDOW.setProperty(property, value.encode('utf-8'))
|
WINDOW.setProperty(property, tryEncode(value))
|
||||||
else:
|
else:
|
||||||
return WINDOW.getProperty(property).decode('utf-8')
|
return tryDecode(WINDOW.getProperty(property))
|
||||||
|
|
||||||
def settings(setting, value=None):
|
def settings(setting, value=None):
|
||||||
"""
|
"""
|
||||||
|
@ -323,10 +360,10 @@ def settings(setting, value=None):
|
||||||
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
# Takes string or unicode by default!
|
# Takes string or unicode by default!
|
||||||
addon.setSetting(setting, value.encode('utf-8'))
|
addon.setSetting(setting, tryEncode(value))
|
||||||
else:
|
else:
|
||||||
# Should return unicode by default, but just in case
|
# Should return unicode by default, but just in case
|
||||||
return addon.getSetting(setting).decode('utf-8')
|
return tryDecode(addon.getSetting(setting))
|
||||||
|
|
||||||
def language(stringid):
|
def language(stringid):
|
||||||
# Central string retrieval
|
# Central string retrieval
|
||||||
|
@ -334,40 +371,39 @@ def language(stringid):
|
||||||
string = addon.getLocalizedString(stringid) #returns unicode object
|
string = addon.getLocalizedString(stringid) #returns unicode object
|
||||||
return string
|
return string
|
||||||
|
|
||||||
def kodiSQL(type="video"):
|
def kodiSQL(media_type="video"):
|
||||||
|
|
||||||
if type == "emby":
|
if media_type == "emby":
|
||||||
dbPath = xbmc.translatePath("special://database/emby.db").decode('utf-8')
|
dbPath = tryDecode(xbmc.translatePath("special://database/emby.db"))
|
||||||
elif type == "music":
|
elif media_type == "music":
|
||||||
dbPath = getKodiMusicDBPath()
|
dbPath = getKodiMusicDBPath()
|
||||||
elif type == "texture":
|
elif media_type == "texture":
|
||||||
dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8')
|
dbPath = tryDecode(xbmc.translatePath(
|
||||||
|
"special://database/Textures13.db"))
|
||||||
else:
|
else:
|
||||||
dbPath = getKodiVideoDBPath()
|
dbPath = getKodiVideoDBPath()
|
||||||
|
|
||||||
connection = sqlite3.connect(dbPath)
|
connection = sqlite3.connect(dbPath)
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
def getKodiVideoDBPath():
|
def getKodiVideoDBPath():
|
||||||
|
|
||||||
kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2]
|
|
||||||
dbVersion = {
|
dbVersion = {
|
||||||
|
|
||||||
"13": 78, # Gotham
|
"13": 78, # Gotham
|
||||||
"14": 90, # Helix
|
"14": 90, # Helix
|
||||||
"15": 93, # Isengard
|
"15": 93, # Isengard
|
||||||
"16": 99, # Jarvis
|
"16": 99, # Jarvis
|
||||||
"17":104 # Krypton
|
"17": 107 # Krypton
|
||||||
}
|
}
|
||||||
|
|
||||||
dbPath = xbmc.translatePath(
|
dbPath = tryDecode(xbmc.translatePath(
|
||||||
"special://database/MyVideos%s.db"
|
"special://database/MyVideos%s.db"
|
||||||
% dbVersion.get(kodibuild, "")).decode('utf-8')
|
% dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")))
|
||||||
return dbPath
|
return dbPath
|
||||||
|
|
||||||
def getKodiMusicDBPath():
|
def getKodiMusicDBPath():
|
||||||
|
|
||||||
kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2]
|
|
||||||
dbVersion = {
|
dbVersion = {
|
||||||
|
|
||||||
"13": 46, # Gotham
|
"13": 46, # Gotham
|
||||||
|
@ -377,9 +413,9 @@ def getKodiMusicDBPath():
|
||||||
"17": 60 # Krypton
|
"17": 60 # Krypton
|
||||||
}
|
}
|
||||||
|
|
||||||
dbPath = xbmc.translatePath(
|
dbPath = tryDecode(xbmc.translatePath(
|
||||||
"special://database/MyMusic%s.db"
|
"special://database/MyMusic%s.db"
|
||||||
% dbVersion.get(kodibuild, "")).decode('utf-8')
|
% dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")))
|
||||||
return dbPath
|
return dbPath
|
||||||
|
|
||||||
def getScreensaver():
|
def getScreensaver():
|
||||||
|
@ -394,11 +430,7 @@ def getScreensaver():
|
||||||
'setting': "screensaver.mode"
|
'setting': "screensaver.mode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = xbmc.executeJSONRPC(json.dumps(query))
|
return json.loads(xbmc.executeJSONRPC(json.dumps(query)))['result']['value']
|
||||||
result = json.loads(result)
|
|
||||||
screensaver = result['result']['value']
|
|
||||||
|
|
||||||
return screensaver
|
|
||||||
|
|
||||||
def setScreensaver(value):
|
def setScreensaver(value):
|
||||||
# Toggle the screensaver
|
# Toggle the screensaver
|
||||||
|
@ -413,21 +445,19 @@ def setScreensaver(value):
|
||||||
'value': value
|
'value': value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = xbmc.executeJSONRPC(json.dumps(query))
|
logMsg("PLEX", "Toggling screensaver: %s %s" % (value, xbmc.executeJSONRPC(json.dumps(query))), 1)
|
||||||
logMsg("PLEX", "Toggling screensaver: %s %s" % (value, result), 1)
|
|
||||||
|
|
||||||
def reset():
|
def reset():
|
||||||
|
|
||||||
dialog = xbmcgui.Dialog()
|
dialog = xbmcgui.Dialog()
|
||||||
|
|
||||||
resp = dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?")
|
if dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?") == 0:
|
||||||
if resp == 0:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# first stop any db sync
|
# first stop any db sync
|
||||||
window('emby_shouldStop', value="true")
|
window('plex_shouldStop', value="true")
|
||||||
count = 10
|
count = 10
|
||||||
while window('emby_dbScan') == "true":
|
while window('plex_dbScan') == "true":
|
||||||
logMsg("PLEX", "Sync is running, will retry: %s..." % count)
|
logMsg("PLEX", "Sync is running, will retry: %s..." % count)
|
||||||
count -= 1
|
count -= 1
|
||||||
if count == 0:
|
if count == 0:
|
||||||
|
@ -442,7 +472,7 @@ def reset():
|
||||||
deleteNodes()
|
deleteNodes()
|
||||||
|
|
||||||
# Wipe the kodi databases
|
# Wipe the kodi databases
|
||||||
logMsg("EMBY", "Resetting the Kodi video database.", 0)
|
logMsg("Plex", "Resetting the Kodi video database.", 0)
|
||||||
connection = kodiSQL('video')
|
connection = kodiSQL('video')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||||
|
@ -455,7 +485,7 @@ def reset():
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
if settings('enableMusic') == "true":
|
if settings('enableMusic') == "true":
|
||||||
logMsg("EMBY", "Resetting the Kodi music database.")
|
logMsg("Plex", "Resetting the Kodi music database.")
|
||||||
connection = kodiSQL('music')
|
connection = kodiSQL('music')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||||
|
@ -467,8 +497,8 @@ def reset():
|
||||||
connection.commit()
|
connection.commit()
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
# Wipe the emby database
|
# Wipe the Plex database
|
||||||
logMsg("EMBY", "Resetting the Emby database.", 0)
|
logMsg("Plex", "Resetting the Emby database.", 0)
|
||||||
connection = kodiSQL('emby')
|
connection = kodiSQL('emby')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||||
|
@ -483,21 +513,25 @@ def reset():
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
# Offer to wipe cached thumbnails
|
# Offer to wipe cached thumbnails
|
||||||
resp = dialog.yesno("Warning", "Removed all cached artwork?")
|
resp = dialog.yesno("Warning", "Remove all cached artwork?")
|
||||||
if resp:
|
if resp:
|
||||||
logMsg("EMBY", "Resetting all cached artwork.", 0)
|
logMsg("EMBY", "Resetting all cached artwork.", 0)
|
||||||
# Remove all existing textures first
|
# Remove all existing textures first
|
||||||
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
|
path = tryDecode(xbmc.translatePath("special://thumbnails/"))
|
||||||
if xbmcvfs.exists(path):
|
if xbmcvfs.exists(path):
|
||||||
allDirs, allFiles = xbmcvfs.listdir(path)
|
allDirs, allFiles = xbmcvfs.listdir(path)
|
||||||
for dir in allDirs:
|
for dir in allDirs:
|
||||||
allDirs, allFiles = xbmcvfs.listdir(path+dir)
|
allDirs, allFiles = xbmcvfs.listdir(path+dir)
|
||||||
for file in allFiles:
|
for file in allFiles:
|
||||||
if os.path.supports_unicode_filenames:
|
if os.path.supports_unicode_filenames:
|
||||||
xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8')))
|
xbmcvfs.delete(os.path.join(
|
||||||
|
path + tryDecode(dir),
|
||||||
|
tryDecode(file)))
|
||||||
else:
|
else:
|
||||||
xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file))
|
xbmcvfs.delete(os.path.join(
|
||||||
|
tryEncode(path) + dir,
|
||||||
|
file))
|
||||||
|
|
||||||
# remove all existing data from texture DB
|
# remove all existing data from texture DB
|
||||||
connection = kodiSQL('texture')
|
connection = kodiSQL('texture')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
@ -509,8 +543,8 @@ def reset():
|
||||||
cursor.execute("DELETE FROM " + tableName)
|
cursor.execute("DELETE FROM " + tableName)
|
||||||
connection.commit()
|
connection.commit()
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
# reset the install run flag
|
# reset the install run flag
|
||||||
settings('SyncInstallRunDone', value="false")
|
settings('SyncInstallRunDone', value="false")
|
||||||
|
|
||||||
# Remove emby info
|
# Remove emby info
|
||||||
|
@ -518,9 +552,9 @@ def reset():
|
||||||
if resp:
|
if resp:
|
||||||
# Delete the settings
|
# Delete the settings
|
||||||
addon = xbmcaddon.Addon()
|
addon = xbmcaddon.Addon()
|
||||||
addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
|
addondir = tryDecode(xbmc.translatePath(addon.getAddonInfo('profile')))
|
||||||
dataPath = "%ssettings.xml" % addondir
|
dataPath = "%ssettings.xml" % addondir
|
||||||
xbmcvfs.delete(dataPath.encode('utf-8'))
|
xbmcvfs.delete(tryEncode(dataPath))
|
||||||
logMsg("PLEX", "Deleting: settings.xml", 1)
|
logMsg("PLEX", "Deleting: settings.xml", 1)
|
||||||
|
|
||||||
dialog.ok(
|
dialog.ok(
|
||||||
|
@ -532,7 +566,7 @@ def profiling(sortby="cumulative"):
|
||||||
# Will print results to Kodi log
|
# Will print results to Kodi log
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
|
|
||||||
pr = cProfile.Profile()
|
pr = cProfile.Profile()
|
||||||
|
|
||||||
pr.enable()
|
pr.enable()
|
||||||
|
@ -575,8 +609,8 @@ def normalize_nodes(text):
|
||||||
# Remove dots from the last character as windows can not have directories
|
# Remove dots from the last character as windows can not have directories
|
||||||
# with dots at the end
|
# with dots at the end
|
||||||
text = text.rstrip('.')
|
text = text.rstrip('.')
|
||||||
text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
|
text = tryEncode(unicodedata.normalize('NFKD', unicode(text, 'utf-8')))
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def normalize_string(text):
|
def normalize_string(text):
|
||||||
|
@ -594,7 +628,7 @@ def normalize_string(text):
|
||||||
# Remove dots from the last character as windows can not have directories
|
# Remove dots from the last character as windows can not have directories
|
||||||
# with dots at the end
|
# with dots at the end
|
||||||
text = text.rstrip('.')
|
text = text.rstrip('.')
|
||||||
text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
|
text = tryEncode(unicodedata.normalize('NFKD', unicode(text, 'utf-8')))
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
@ -619,7 +653,7 @@ def guisettingsXML():
|
||||||
"""
|
"""
|
||||||
Returns special://userdata/guisettings.xml as an etree xml root element
|
Returns special://userdata/guisettings.xml as an etree xml root element
|
||||||
"""
|
"""
|
||||||
path = xbmc.translatePath("special://profile/").decode('utf-8')
|
path = tryDecode(xbmc.translatePath("special://profile/"))
|
||||||
xmlpath = "%sguisettings.xml" % path
|
xmlpath = "%sguisettings.xml" % path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -678,7 +712,7 @@ def advancedSettingsXML():
|
||||||
usetags : set to "false"
|
usetags : set to "false"
|
||||||
findremotethumbs : set to "false"
|
findremotethumbs : set to "false"
|
||||||
"""
|
"""
|
||||||
path = xbmc.translatePath("special://profile/").decode('utf-8')
|
path = tryDecode(xbmc.translatePath("special://profile/"))
|
||||||
xmlpath = "%sadvancedsettings.xml" % path
|
xmlpath = "%sadvancedsettings.xml" % path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -708,7 +742,7 @@ def advancedSettingsXML():
|
||||||
|
|
||||||
def sourcesXML():
|
def sourcesXML():
|
||||||
# To make Master lock compatible
|
# To make Master lock compatible
|
||||||
path = xbmc.translatePath("special://profile/").decode('utf-8')
|
path = tryDecode(xbmc.translatePath("special://profile/"))
|
||||||
xmlpath = "%ssources.xml" % path
|
xmlpath = "%ssources.xml" % path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -717,7 +751,7 @@ def sourcesXML():
|
||||||
root = etree.Element('sources')
|
root = etree.Element('sources')
|
||||||
else:
|
else:
|
||||||
root = xmlparse.getroot()
|
root = xmlparse.getroot()
|
||||||
|
|
||||||
|
|
||||||
video = root.find('video')
|
video = root.find('video')
|
||||||
if video is None:
|
if video is None:
|
||||||
|
@ -729,7 +763,7 @@ def sourcesXML():
|
||||||
for source in root.findall('.//path'):
|
for source in root.findall('.//path'):
|
||||||
if source.text == "smb://":
|
if source.text == "smb://":
|
||||||
count -= 1
|
count -= 1
|
||||||
|
|
||||||
if count == 0:
|
if count == 0:
|
||||||
# sources already set
|
# sources already set
|
||||||
break
|
break
|
||||||
|
@ -749,7 +783,7 @@ def sourcesXML():
|
||||||
|
|
||||||
def passwordsXML():
|
def passwordsXML():
|
||||||
# To add network credentials
|
# To add network credentials
|
||||||
path = xbmc.translatePath("special://userdata/").decode('utf-8')
|
path = tryDecode(xbmc.translatePath("special://userdata/"))
|
||||||
xmlpath = "%spasswords.xml" % path
|
xmlpath = "%spasswords.xml" % path
|
||||||
logMsg('passwordsXML', 'Path to passwords.xml: %s' % xmlpath, 1)
|
logMsg('passwordsXML', 'Path to passwords.xml: %s' % xmlpath, 1)
|
||||||
|
|
||||||
|
@ -775,9 +809,7 @@ def passwordsXML():
|
||||||
|
|
||||||
elif option == 1:
|
elif option == 1:
|
||||||
# User selected remove
|
# User selected remove
|
||||||
iterator = root.getiterator('passwords')
|
for paths in root.getiterator('passwords'):
|
||||||
|
|
||||||
for paths in iterator:
|
|
||||||
for path in paths:
|
for path in paths:
|
||||||
if path.find('.//from').text == "smb://%s/" % credentials:
|
if path.find('.//from').text == "smb://%s/" % credentials:
|
||||||
paths.remove(path)
|
paths.remove(path)
|
||||||
|
@ -787,8 +819,8 @@ def passwordsXML():
|
||||||
etree.ElementTree(root).write(xmlpath)
|
etree.ElementTree(root).write(xmlpath)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logMsg("EMBY", "Failed to find saved server: %s in passwords.xml" % credentials, 1)
|
logMsg("Plex", "Failed to find saved server: %s in passwords.xml" % credentials, 1)
|
||||||
|
|
||||||
settings('networkCreds', value="")
|
settings('networkCreds', value="")
|
||||||
xbmcgui.Dialog().notification(
|
xbmcgui.Dialog().notification(
|
||||||
heading='PlexKodiConnect',
|
heading='PlexKodiConnect',
|
||||||
|
@ -842,7 +874,7 @@ def passwordsXML():
|
||||||
# Force Kodi to see the credentials without restarting
|
# Force Kodi to see the credentials without restarting
|
||||||
xbmcvfs.exists(topath)
|
xbmcvfs.exists(topath)
|
||||||
|
|
||||||
# Add credentials
|
# Add credentials
|
||||||
settings('networkCreds', value="%s" % server)
|
settings('networkCreds', value="%s" % server)
|
||||||
logMsg("PLEX", "Added server: %s to passwords.xml" % server, 1)
|
logMsg("PLEX", "Added server: %s to passwords.xml" % server, 1)
|
||||||
# Prettify and write to file
|
# Prettify and write to file
|
||||||
|
@ -850,7 +882,7 @@ def passwordsXML():
|
||||||
indent(root)
|
indent(root)
|
||||||
except: pass
|
except: pass
|
||||||
etree.ElementTree(root).write(xmlpath)
|
etree.ElementTree(root).write(xmlpath)
|
||||||
|
|
||||||
# dialog.notification(
|
# dialog.notification(
|
||||||
# heading="PlexKodiConnect",
|
# heading="PlexKodiConnect",
|
||||||
# message="Added to passwords.xml",
|
# message="Added to passwords.xml",
|
||||||
|
@ -862,7 +894,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
|
||||||
"""
|
"""
|
||||||
Feed with tagname as unicode
|
Feed with tagname as unicode
|
||||||
"""
|
"""
|
||||||
path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
|
path = tryDecode(xbmc.translatePath("special://profile/playlists/video/"))
|
||||||
if viewtype == "mixed":
|
if viewtype == "mixed":
|
||||||
plname = "%s - %s" % (tagname, mediatype)
|
plname = "%s - %s" % (tagname, mediatype)
|
||||||
xsppath = "%sPlex %s - %s.xsp" % (path, viewid, mediatype)
|
xsppath = "%sPlex %s - %s.xsp" % (path, viewid, mediatype)
|
||||||
|
@ -871,17 +903,17 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
|
||||||
xsppath = "%sPlex %s.xsp" % (path, viewid)
|
xsppath = "%sPlex %s.xsp" % (path, viewid)
|
||||||
|
|
||||||
# Create the playlist directory
|
# Create the playlist directory
|
||||||
if not xbmcvfs.exists(path.encode('utf-8')):
|
if not xbmcvfs.exists(tryEncode(path)):
|
||||||
logMsg("PLEX", "Creating directory: %s" % path, 1)
|
logMsg("PLEX", "Creating directory: %s" % path, 1)
|
||||||
xbmcvfs.mkdirs(path.encode('utf-8'))
|
xbmcvfs.mkdirs(tryEncode(path))
|
||||||
|
|
||||||
# Only add the playlist if it doesn't already exists
|
# Only add the playlist if it doesn't already exists
|
||||||
if xbmcvfs.exists(xsppath.encode('utf-8')):
|
if xbmcvfs.exists(tryEncode(xsppath)):
|
||||||
logMsg('Path %s does exist' % xsppath, 1)
|
logMsg('Path %s does exist' % xsppath, 1)
|
||||||
if delete:
|
if delete:
|
||||||
xbmcvfs.delete(xsppath.encode('utf-8'))
|
xbmcvfs.delete(tryEncode(xsppath))
|
||||||
logMsg("PLEX", "Successfully removed playlist: %s." % tagname, 1)
|
logMsg("PLEX", "Successfully removed playlist: %s." % tagname, 1)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Using write process since there's no guarantee the xml declaration works with etree
|
# Using write process since there's no guarantee the xml declaration works with etree
|
||||||
|
@ -892,12 +924,12 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
|
||||||
}
|
}
|
||||||
logMsg("Plex", "Writing playlist file to: %s" % xsppath, 1)
|
logMsg("Plex", "Writing playlist file to: %s" % xsppath, 1)
|
||||||
try:
|
try:
|
||||||
f = xbmcvfs.File(xsppath.encode('utf-8'), 'wb')
|
f = xbmcvfs.File(tryEncode(xsppath), 'wb')
|
||||||
except:
|
except:
|
||||||
logMsg("Plex", "Failed to create playlist: %s" % xsppath, -1)
|
logMsg("Plex", "Failed to create playlist: %s" % xsppath, -1)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
f.write((
|
f.write(tryEncode(
|
||||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
|
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
|
||||||
'<smartplaylist type="%s">\n\t'
|
'<smartplaylist type="%s">\n\t'
|
||||||
'<name>Plex %s</name>\n\t'
|
'<name>Plex %s</name>\n\t'
|
||||||
|
@ -906,47 +938,67 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
|
||||||
'<value>%s</value>\n\t'
|
'<value>%s</value>\n\t'
|
||||||
'</rule>\n'
|
'</rule>\n'
|
||||||
'</smartplaylist>\n'
|
'</smartplaylist>\n'
|
||||||
% (itemtypes.get(mediatype, mediatype), plname, tagname))
|
% (itemtypes.get(mediatype, mediatype), plname, tagname)))
|
||||||
.encode('utf-8'))
|
|
||||||
f.close()
|
f.close()
|
||||||
logMsg("Plex", "Successfully added playlist: %s" % tagname)
|
logMsg("Plex", "Successfully added playlist: %s" % tagname)
|
||||||
|
|
||||||
def deletePlaylists():
|
def deletePlaylists():
|
||||||
|
|
||||||
# Clean up the playlists
|
# Clean up the playlists
|
||||||
path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
|
path = tryDecode(xbmc.translatePath("special://profile/playlists/video/"))
|
||||||
dirs, files = xbmcvfs.listdir(path.encode('utf-8'))
|
dirs, files = xbmcvfs.listdir(tryEncode(path))
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.decode('utf-8').startswith('Plex'):
|
if tryDecode(file).startswith('Plex'):
|
||||||
xbmcvfs.delete(("%s%s" % (path, file.decode('utf-8'))).encode('utf-8'))
|
xbmcvfs.delete(tryEncode("%s%s" % (path, tryDecode(file))))
|
||||||
|
|
||||||
def deleteNodes():
|
def deleteNodes():
|
||||||
|
|
||||||
# Clean up video nodes
|
# Clean up video nodes
|
||||||
import shutil
|
import shutil
|
||||||
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
path = tryDecode(xbmc.translatePath("special://profile/library/video/"))
|
||||||
dirs, files = xbmcvfs.listdir(path.encode('utf-8'))
|
dirs, files = xbmcvfs.listdir(tryEncode(path))
|
||||||
for dir in dirs:
|
for dir in dirs:
|
||||||
if dir.decode('utf-8').startswith('Plex'):
|
if tryDecode(dir).startswith('Plex'):
|
||||||
try:
|
try:
|
||||||
shutil.rmtree("%s%s" % (path, dir.decode('utf-8')))
|
shutil.rmtree("%s%s" % (path, tryDecode(dir)))
|
||||||
except:
|
except:
|
||||||
logMsg("PLEX", "Failed to delete directory: %s" % dir.decode('utf-8'))
|
logMsg("PLEX", "Failed to delete directory: %s"
|
||||||
|
% tryDecode(dir))
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.decode('utf-8').startswith('plex'):
|
if tryDecode(file).startswith('plex'):
|
||||||
try:
|
try:
|
||||||
xbmcvfs.delete(("%s%s" % (path, file.decode('utf-8'))).encode('utf-8'))
|
xbmcvfs.delete(tryEncode("%s%s" % (path, tryDecode(file))))
|
||||||
except:
|
except:
|
||||||
logMsg("PLEX", "Failed to file: %s" % file.decode('utf-8'))
|
logMsg("PLEX", "Failed to file: %s" % tryDecode(file))
|
||||||
|
|
||||||
def try_encode(text, encoding="utf-8"):
|
|
||||||
try:
|
|
||||||
return text.encode(encoding,"ignore")
|
|
||||||
except:
|
|
||||||
return text
|
|
||||||
|
|
||||||
def try_decode(text, encoding="utf-8"):
|
def tryEncode(uniString, encoding='utf-8'):
|
||||||
|
"""
|
||||||
|
Will try to encode uniString (in unicode) to encoding. This possibly
|
||||||
|
fails with e.g. Android TV's Python, which does not accept arguments for
|
||||||
|
string.encode()
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return text.decode(encoding,"ignore")
|
uniString = uniString.encode(encoding, "ignore")
|
||||||
except:
|
except TypeError:
|
||||||
return text
|
uniString = uniString.encode()
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
# already encoded
|
||||||
|
pass
|
||||||
|
return uniString
|
||||||
|
|
||||||
|
|
||||||
|
def tryDecode(string, encoding='utf-8'):
|
||||||
|
"""
|
||||||
|
Will try to decode string (encoded) using encoding. This possibly
|
||||||
|
fails with e.g. Android TV's Python, which does not accept arguments for
|
||||||
|
string.encode()
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
string = string.decode(encoding, "ignore")
|
||||||
|
except TypeError:
|
||||||
|
string = string.decode()
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
# Already in unicode - e.g. sometimes file paths
|
||||||
|
pass
|
||||||
|
return string
|
||||||
|
|
|
@ -54,16 +54,16 @@ class VideoNodes(object):
|
||||||
mediatype = mediatypes[mediatype]
|
mediatype = mediatypes[mediatype]
|
||||||
|
|
||||||
window = utils.window
|
window = utils.window
|
||||||
kodiversion = self.kodiversion
|
|
||||||
|
|
||||||
if viewtype == "mixed":
|
if viewtype == "mixed":
|
||||||
dirname = "%s-%s" % (viewid, mediatype)
|
dirname = "%s-%s" % (viewid, mediatype)
|
||||||
else:
|
else:
|
||||||
dirname = viewid
|
dirname = viewid
|
||||||
|
|
||||||
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
path = utils.tryDecode(xbmc.translatePath(
|
||||||
nodepath = xbmc.translatePath(
|
"special://profile/library/video/"))
|
||||||
"special://profile/library/video/Plex-%s/" % dirname).decode('utf-8')
|
nodepath = utils.tryDecode(xbmc.translatePath(
|
||||||
|
"special://profile/library/video/Plex-%s/" % dirname))
|
||||||
|
|
||||||
# Verify the video directory
|
# Verify the video directory
|
||||||
# KODI BUG
|
# KODI BUG
|
||||||
|
@ -71,20 +71,22 @@ class VideoNodes(object):
|
||||||
# so try creating a file
|
# so try creating a file
|
||||||
if utils.IfExists(path) is False:
|
if utils.IfExists(path) is False:
|
||||||
shutil.copytree(
|
shutil.copytree(
|
||||||
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
|
src=utils.tryDecode(xbmc.translatePath(
|
||||||
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
|
"special://xbmc/system/library/video")),
|
||||||
|
dst=utils.tryDecode(xbmc.translatePath(
|
||||||
|
"special://profile/library/video")))
|
||||||
|
|
||||||
# Create the node directory
|
# Create the node directory
|
||||||
if mediatype != "photo":
|
if mediatype != "photos":
|
||||||
if utils.IfExists(nodepath) is False:
|
if utils.IfExists(nodepath) is False:
|
||||||
# folder does not exist yet
|
# folder does not exist yet
|
||||||
self.logMsg('Creating folder %s' % nodepath, 1)
|
self.logMsg('Creating folder %s' % nodepath, 1)
|
||||||
xbmcvfs.mkdirs(nodepath.encode('utf-8'))
|
xbmcvfs.mkdirs(utils.tryEncode(nodepath))
|
||||||
if delete:
|
if delete:
|
||||||
dirs, files = xbmcvfs.listdir(nodepath.encode('utf-8'))
|
dirs, files = xbmcvfs.listdir(utils.tryEncode(nodepath))
|
||||||
for file in files:
|
for file in files:
|
||||||
xbmcvfs.delete(
|
xbmcvfs.delete(utils.tryEncode(
|
||||||
(nodepath + file.decode('utf-8')).encode('utf-8'))
|
(nodepath + utils.tryDecode(file))))
|
||||||
|
|
||||||
self.logMsg("Sucessfully removed videonode: %s."
|
self.logMsg("Sucessfully removed videonode: %s."
|
||||||
% tagname, 1)
|
% tagname, 1)
|
||||||
|
@ -96,16 +98,16 @@ class VideoNodes(object):
|
||||||
path = "library://video/Plex-%s/" % dirname
|
path = "library://video/Plex-%s/" % dirname
|
||||||
for i in range(1, indexnumber):
|
for i in range(1, indexnumber):
|
||||||
# Verify to make sure we don't create duplicates
|
# Verify to make sure we don't create duplicates
|
||||||
if window('Emby.nodes.%s.index' % i) == path:
|
if window('Plex.nodes.%s.index' % i) == path:
|
||||||
return
|
return
|
||||||
|
|
||||||
if mediatype == "photo":
|
if mediatype == "photos":
|
||||||
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=getsubfolders" % indexnumber
|
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=getsubfolders" % indexnumber
|
||||||
|
|
||||||
window('Emby.nodes.%s.index' % indexnumber, value=path)
|
window('Plex.nodes.%s.index' % indexnumber, value=path)
|
||||||
|
|
||||||
# Root
|
# Root
|
||||||
if not mediatype == "photo":
|
if not mediatype == "photos":
|
||||||
if viewtype == "mixed":
|
if viewtype == "mixed":
|
||||||
specialtag = "%s-%s" % (tagname, mediatype)
|
specialtag = "%s-%s" % (tagname, mediatype)
|
||||||
root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0)
|
root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0)
|
||||||
|
@ -215,14 +217,14 @@ class VideoNodes(object):
|
||||||
label = stringid
|
label = stringid
|
||||||
|
|
||||||
# Set window properties
|
# Set window properties
|
||||||
if (mediatype == "homevideos" or mediatype == "photo") and nodetype == "all":
|
if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all":
|
||||||
# Custom query
|
# Custom query
|
||||||
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=browsecontent&type=%s"
|
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=browseplex&type=%s"
|
||||||
% (tagname, mediatype))
|
% (viewid, mediatype))
|
||||||
elif (mediatype == "homevideos" or mediatype == "photo"):
|
elif (mediatype == "homevideos" or mediatype == "photos"):
|
||||||
# Custom query
|
# Custom query
|
||||||
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=browsecontent&type=%s&folderid=%s"
|
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=browseplex&type=%s&folderid=%s"
|
||||||
% (tagname, mediatype, nodetype))
|
% (viewid, mediatype, nodetype))
|
||||||
elif nodetype == "nextepisodes":
|
elif nodetype == "nextepisodes":
|
||||||
# Custom query
|
# Custom query
|
||||||
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=nextup&limit=%s" % (tagname, limit)
|
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=nextup&limit=%s" % (tagname, limit)
|
||||||
|
@ -231,7 +233,7 @@ class VideoNodes(object):
|
||||||
# Custom query
|
# Custom query
|
||||||
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=recentepisodes&type=%s&tagname=%s&limit=%s"
|
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=recentepisodes&type=%s&tagname=%s&limit=%s"
|
||||||
% (viewid, mediatype, tagname, limit))
|
% (viewid, mediatype, tagname, limit))
|
||||||
elif kodiversion == 14 and nodetype == "inprogressepisodes":
|
elif self.kodiversion == 14 and nodetype == "inprogressepisodes":
|
||||||
# Custom query
|
# Custom query
|
||||||
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=inprogressepisodes&limit=%s" % (tagname, limit)
|
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=inprogressepisodes&limit=%s" % (tagname, limit)
|
||||||
elif nodetype == 'ondeck':
|
elif nodetype == 'ondeck':
|
||||||
|
@ -245,10 +247,14 @@ class VideoNodes(object):
|
||||||
else:
|
else:
|
||||||
path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype)
|
path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype)
|
||||||
|
|
||||||
if mediatype == "photo":
|
if mediatype == "photos":
|
||||||
windowpath = "ActivateWindow(Pictures,%s,return)" % path
|
windowpath = "ActivateWindow(Pictures,%s,return)" % path
|
||||||
else:
|
else:
|
||||||
windowpath = "ActivateWindow(Video,%s,return)" % path
|
if self.kodiversion >= 17:
|
||||||
|
# Krypton
|
||||||
|
windowpath = "ActivateWindow(Videos,%s,return)" % path
|
||||||
|
else:
|
||||||
|
windowpath = "ActivateWindow(Video,%s,return)" % path
|
||||||
|
|
||||||
if nodetype == "all":
|
if nodetype == "all":
|
||||||
|
|
||||||
|
@ -257,24 +263,24 @@ class VideoNodes(object):
|
||||||
else:
|
else:
|
||||||
templabel = label
|
templabel = label
|
||||||
|
|
||||||
embynode = "Emby.nodes.%s" % indexnumber
|
embynode = "Plex.nodes.%s" % indexnumber
|
||||||
window('%s.title' % embynode, value=templabel)
|
window('%s.title' % embynode, value=templabel)
|
||||||
window('%s.path' % embynode, value=windowpath)
|
window('%s.path' % embynode, value=windowpath)
|
||||||
window('%s.content' % embynode, value=path)
|
window('%s.content' % embynode, value=path)
|
||||||
window('%s.type' % embynode, value=mediatype)
|
window('%s.type' % embynode, value=mediatype)
|
||||||
else:
|
else:
|
||||||
embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
|
embynode = "Plex.nodes.%s.%s" % (indexnumber, nodetype)
|
||||||
window('%s.title' % embynode, value=label)
|
window('%s.title' % embynode, value=label)
|
||||||
window('%s.path' % embynode, value=windowpath)
|
window('%s.path' % embynode, value=windowpath)
|
||||||
window('%s.content' % embynode, value=path)
|
window('%s.content' % embynode, value=path)
|
||||||
|
|
||||||
if mediatype == "photo":
|
if mediatype == "photos":
|
||||||
# For photos, we do not create a node in videos but we do want the window props
|
# For photos, we do not create a node in videos but we do want the window props
|
||||||
# to be created.
|
# to be created.
|
||||||
# To do: add our photos nodes to kodi picture sources somehow
|
# To do: add our photos nodes to kodi picture sources somehow
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if xbmcvfs.exists(nodeXML.encode('utf-8')):
|
if xbmcvfs.exists(utils.tryEncode(nodeXML)):
|
||||||
# Don't recreate xml if already exists
|
# Don't recreate xml if already exists
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -298,8 +304,12 @@ class VideoNodes(object):
|
||||||
elif nodetype == "recent":
|
elif nodetype == "recent":
|
||||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
|
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
|
||||||
etree.SubElement(root, 'limit').text = limit
|
etree.SubElement(root, 'limit').text = limit
|
||||||
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
|
if utils.settings('MovieShowWatched') == 'false':
|
||||||
etree.SubElement(rule, 'value').text = "0"
|
rule = etree.SubElement(root,
|
||||||
|
'rule',
|
||||||
|
{'field': "playcount",
|
||||||
|
'operator': "is"})
|
||||||
|
etree.SubElement(rule, 'value').text = "0"
|
||||||
|
|
||||||
elif nodetype == "inprogress":
|
elif nodetype == "inprogress":
|
||||||
etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
|
etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
|
||||||
|
@ -358,19 +368,26 @@ class VideoNodes(object):
|
||||||
|
|
||||||
window = utils.window
|
window = utils.window
|
||||||
|
|
||||||
tagname = tagname.encode('utf-8')
|
tagname = utils.tryEncode(tagname)
|
||||||
cleantagname = utils.normalize_nodes(tagname)
|
cleantagname = utils.normalize_nodes(tagname)
|
||||||
nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
nodepath = utils.tryDecode(xbmc.translatePath(
|
||||||
|
"special://profile/library/video/"))
|
||||||
nodeXML = "%splex_%s.xml" % (nodepath, cleantagname)
|
nodeXML = "%splex_%s.xml" % (nodepath, cleantagname)
|
||||||
path = "library://video/plex_%s.xml" % cleantagname
|
path = "library://video/plex_%s.xml" % cleantagname
|
||||||
windowpath = "ActivateWindow(Video,%s,return)" % path
|
if self.kodiversion >= 17:
|
||||||
|
# Krypton
|
||||||
|
windowpath = "ActivateWindow(Videos,%s,return)" % path
|
||||||
|
else:
|
||||||
|
windowpath = "ActivateWindow(Video,%s,return)" % path
|
||||||
|
|
||||||
# Create the video node directory
|
# Create the video node directory
|
||||||
if not xbmcvfs.exists(nodepath):
|
if not xbmcvfs.exists(nodepath):
|
||||||
# We need to copy over the default items
|
# We need to copy over the default items
|
||||||
shutil.copytree(
|
shutil.copytree(
|
||||||
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
|
src=utils.tryDecode(xbmc.translatePath(
|
||||||
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
|
"special://xbmc/system/library/video")),
|
||||||
|
dst=utils.tryDecode(xbmc.translatePath(
|
||||||
|
"special://profile/library/video")))
|
||||||
xbmcvfs.exists(path)
|
xbmcvfs.exists(path)
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
|
@ -380,7 +397,7 @@ class VideoNodes(object):
|
||||||
'channels': 30173
|
'channels': 30173
|
||||||
}
|
}
|
||||||
label = utils.language(labels[tagname])
|
label = utils.language(labels[tagname])
|
||||||
embynode = "Emby.nodes.%s" % indexnumber
|
embynode = "Plex.nodes.%s" % indexnumber
|
||||||
window('%s.title' % embynode, value=label)
|
window('%s.title' % embynode, value=label)
|
||||||
window('%s.path' % embynode, value=windowpath)
|
window('%s.path' % embynode, value=windowpath)
|
||||||
window('%s.content' % embynode, value=path)
|
window('%s.content' % embynode, value=path)
|
||||||
|
@ -409,7 +426,7 @@ class VideoNodes(object):
|
||||||
window = utils.window
|
window = utils.window
|
||||||
|
|
||||||
self.logMsg("Clearing nodes properties.", 1)
|
self.logMsg("Clearing nodes properties.", 1)
|
||||||
embyprops = window('Emby.nodes.total')
|
plexprops = window('Plex.nodes.total')
|
||||||
propnames = [
|
propnames = [
|
||||||
|
|
||||||
"index","path","title","content",
|
"index","path","title","content",
|
||||||
|
@ -424,8 +441,8 @@ class VideoNodes(object):
|
||||||
"inprogressepisodes.content","inprogressepisodes.path"
|
"inprogressepisodes.content","inprogressepisodes.path"
|
||||||
]
|
]
|
||||||
|
|
||||||
if embyprops:
|
if plexprops:
|
||||||
totalnodes = int(embyprops)
|
totalnodes = int(plexprops)
|
||||||
for i in range(totalnodes):
|
for i in range(totalnodes):
|
||||||
for prop in propnames:
|
for prop in propnames:
|
||||||
window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)
|
window('Plex.nodes.%s.%s' % (str(i), prop), clear=True)
|
File diff suppressed because it is too large
Load diff
|
@ -44,6 +44,7 @@ class WebSocket(threading.Thread):
|
||||||
if typus is None:
|
if typus is None:
|
||||||
self.logMsg('No message type, dropping message: %s' % message, -1)
|
self.logMsg('No message type, dropping message: %s' % message, -1)
|
||||||
return False
|
return False
|
||||||
|
self.logMsg('Received message from PMS server: %s' % message, 2)
|
||||||
# Drop everything we're not interested in
|
# Drop everything we're not interested in
|
||||||
if typus not in ('playing', 'timeline'):
|
if typus not in ('playing', 'timeline'):
|
||||||
return True
|
return True
|
||||||
|
@ -87,17 +88,16 @@ class WebSocket(threading.Thread):
|
||||||
uri = "%s/:/websockets/notifications" % server
|
uri = "%s/:/websockets/notifications" % server
|
||||||
if token:
|
if token:
|
||||||
uri += '?X-Plex-Token=%s' % token
|
uri += '?X-Plex-Token=%s' % token
|
||||||
return uri
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
log = self.logMsg
|
|
||||||
# Currently not working due to missing SSL environment
|
|
||||||
sslopt = {}
|
sslopt = {}
|
||||||
if utils.settings('sslverify') == "false":
|
if utils.settings('sslverify') == "false":
|
||||||
sslopt["cert_reqs"] = ssl.CERT_NONE
|
sslopt["cert_reqs"] = ssl.CERT_NONE
|
||||||
|
return uri, sslopt
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
log = self.logMsg
|
||||||
log("----===## Starting WebSocketClient ##===----", 0)
|
log("----===## Starting WebSocketClient ##===----", 0)
|
||||||
|
|
||||||
|
counter = 0
|
||||||
threadStopped = self.threadStopped
|
threadStopped = self.threadStopped
|
||||||
threadSuspended = self.threadSuspended
|
threadSuspended = self.threadSuspended
|
||||||
while not threadStopped():
|
while not threadStopped():
|
||||||
|
@ -122,7 +122,7 @@ class WebSocket(threading.Thread):
|
||||||
pass
|
pass
|
||||||
except websocket.WebSocketConnectionClosedException:
|
except websocket.WebSocketConnectionClosedException:
|
||||||
log("Connection closed, (re)connecting", 0)
|
log("Connection closed, (re)connecting", 0)
|
||||||
uri = self.getUri()
|
uri, sslopt = self.getUri()
|
||||||
try:
|
try:
|
||||||
# Low timeout - let's us shut this thread down!
|
# Low timeout - let's us shut this thread down!
|
||||||
self.ws = websocket.create_connection(
|
self.ws = websocket.create_connection(
|
||||||
|
@ -131,8 +131,15 @@ class WebSocket(threading.Thread):
|
||||||
sslopt=sslopt,
|
sslopt=sslopt,
|
||||||
enable_multithread=True)
|
enable_multithread=True)
|
||||||
except IOError:
|
except IOError:
|
||||||
|
# Server is probably offline
|
||||||
log("Error connecting", 0)
|
log("Error connecting", 0)
|
||||||
self.ws = None
|
self.ws = None
|
||||||
|
counter += 1
|
||||||
|
if counter > 10:
|
||||||
|
log("Repeatedly could not connect to PMS, declaring "
|
||||||
|
"the connection dead", -1)
|
||||||
|
utils.window('plex_online', value='false')
|
||||||
|
counter = 0
|
||||||
xbmc.sleep(1000)
|
xbmc.sleep(1000)
|
||||||
except websocket.WebSocketTimeoutException:
|
except websocket.WebSocketTimeoutException:
|
||||||
log("timeout while connecting, trying again", 0)
|
log("timeout while connecting, trying again", 0)
|
||||||
|
@ -142,6 +149,8 @@ class WebSocket(threading.Thread):
|
||||||
log("Unknown exception encountered in connecting: %s" % e)
|
log("Unknown exception encountered in connecting: %s" % e)
|
||||||
self.ws = None
|
self.ws = None
|
||||||
xbmc.sleep(1000)
|
xbmc.sleep(1000)
|
||||||
|
else:
|
||||||
|
counter = 0
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log("Unknown exception encountered: %s" % e)
|
log("Unknown exception encountered: %s" % e)
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,37 +1,30 @@
|
||||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
<settings>
|
<settings>
|
||||||
<category label="30014"><!-- Connection -->
|
<category label="30014"><!-- Connection -->
|
||||||
<!-- Primary address -->
|
|
||||||
<setting label="39050" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=chooseServer)" option="close" /><!-- Choose Plex Server from a list -->
|
<setting label="39050" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=chooseServer)" option="close" /><!-- Choose Plex Server from a list -->
|
||||||
<setting id="ipaddress" label="30000" type="text" default="" />
|
<setting label="39068" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=enterPMS)" option="close" /><!-- Manually enter Plex Media Server IP -->
|
||||||
<setting id="port" label="30030" type="number" default="32400" />
|
<setting id="plex_servername" label="39067" type="text" default="" enable="false" /><!-- Your current PMS server: -->
|
||||||
|
<setting id="ipaddress" label="39069" type="text" default="" enable="false" /><!-- Current address: -->
|
||||||
|
<setting id="port" label="39070" type="text" default="" enable="false" /><!-- Current port: -->
|
||||||
<setting id="plex_serverowned" label="30031" type="bool" default="true" /><!-- I own this PMS -->
|
<setting id="plex_serverowned" label="30031" type="bool" default="true" /><!-- I own this PMS -->
|
||||||
<setting id="https" label="30243" type="bool" default="false" />
|
<setting id="https" label="30243" type="bool" default="false" />
|
||||||
<setting id="sslverify" subsetting="true" label="30500" type="bool" default="false" visible="eq(-1,true)" />
|
<setting id="sslverify" subsetting="true" label="30500" type="bool" default="false" visible="eq(-1,true)" />
|
||||||
<setting id="sslcert" subsetting="true" label="30501" type="file" default="None" visible="eq(-2,true)" />
|
<setting id="sslcert" subsetting="true" label="30501" type="file" default="None" visible="eq(-2,true)" />
|
||||||
<!-- Secondary address -->
|
|
||||||
<setting id="altip" label="30502" type="bool" default="false" visible="false"/>
|
<setting type="sep" text=""/>
|
||||||
<setting id="secondipaddress" subsetting="true" label="30503" type="text" default="" visible="eq(-1,true)" />
|
|
||||||
<setting id="secondport" subsetting="true" label="30030" type="number" default="8096" visible="eq(-2,true)" />
|
|
||||||
<setting id="secondhttps" subsetting="true" label="30243" type="bool" default="false" visible="eq(-3,true)" />
|
|
||||||
<setting id="secondsslverify" subsetting="true" label="30500" type="bool" default="false" visible="eq(-1,true)" />
|
|
||||||
<setting id="secondsslcert" subsetting="true" label="30501" type="file" default="None" visible="eq(-2,true)" />
|
|
||||||
<!-- User settings -->
|
|
||||||
<setting type="sep" />
|
|
||||||
|
|
||||||
<setting id="enforceUserLogin" label="30536" type="bool" default="false" />
|
<setting id="enforceUserLogin" label="30536" type="bool" default="false" />
|
||||||
|
|
||||||
<setting label="30505" type="action" visible="eq(1,) + !eq(-15,)" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=resetauth)" option="close" />
|
|
||||||
<setting label="30517" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=passwords)" option="close" /><!-- Network credentials -->
|
<setting label="30517" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=passwords)" option="close" /><!-- Network credentials -->
|
||||||
|
<setting label="30505" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=resetauth)" option="close" /><!-- reset connection attempts -->
|
||||||
<setting id="accessToken" type="text" visible="false" default="" />
|
<setting id="accessToken" type="text" visible="false" default="" />
|
||||||
<setting id="pathsub" type="bool" visible="false" default="false" />
|
|
||||||
<setting label="39024" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=reConnect)" option="close" />
|
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="plex.tv"><!-- plex.tv -->
|
<category label="plex.tv"><!-- plex.tv -->
|
||||||
<!-- Primary address -->
|
<setting id="plex_status" label="39071" type="text" default="Not logged in to plex.tv" enable="false" /><!-- Current plex.tv status: -->
|
||||||
|
<setting type="sep" text=""/>
|
||||||
|
|
||||||
<setting id="myplexlogin" label="39025" type="bool" default="true" /> <!-- Log into plex.tv on startup -->
|
<setting id="myplexlogin" label="39025" type="bool" default="true" /> <!-- Log into plex.tv on startup -->
|
||||||
<setting label="39209" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=reConnect)" option="close" />
|
<setting label="39209" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=togglePlexTV)" option="close" />
|
||||||
|
|
||||||
<setting id="plexLogin" label="plex.tv username" type="text" default="" visible="false" />
|
<setting id="plexLogin" label="plex.tv username" type="text" default="" visible="false" />
|
||||||
<setting id="plexhome" label="Plex home in use" type="bool" default="" visible="false" />
|
<setting id="plexhome" label="Plex home in use" type="bool" default="" visible="false" />
|
||||||
|
@ -47,18 +40,19 @@
|
||||||
<setting id="limitindex" type="number" label="30515" default="200" option="int" /><!-- Maximum items to request from the server at once -->
|
<setting id="limitindex" type="number" label="30515" default="200" option="int" /><!-- Maximum items to request from the server at once -->
|
||||||
<setting id="enableTextureCache" label="30512" type="bool" default="true" /> <!-- Force Artwork Caching -->
|
<setting id="enableTextureCache" label="30512" type="bool" default="true" /> <!-- Force Artwork Caching -->
|
||||||
<setting id="imageCacheLimit" type="enum" label="30513" values="Disabled|5|10|15|20|25" default="5" visible="eq(-1,true)" subsetting="true" /> <!-- Limit artwork cache threads -->
|
<setting id="imageCacheLimit" type="enum" label="30513" values="Disabled|5|10|15|20|25" default="5" visible="eq(-1,true)" subsetting="true" /> <!-- Limit artwork cache threads -->
|
||||||
<setting id="syncThreadNumber" type="slider" label="39003" default="5" option="int" range="1,1,20"/>
|
|
||||||
<setting id="serverSync" type="bool" label="30514" default="true" visible="false"/><!-- Enable fast startup (requires server plugin) -->
|
<setting id="serverSync" type="bool" label="30514" default="true" visible="false"/><!-- Enable fast startup (requires server plugin) -->
|
||||||
|
|
||||||
<setting type="lsep" label="39052" /><!-- Background Sync -->
|
<setting type="lsep" label="39052" /><!-- Background Sync -->
|
||||||
<setting id="enableBackgroundSync" type="bool" label="39026" default="true" visible="true"/>
|
<setting id="enableBackgroundSync" type="bool" label="39026" default="true" visible="true"/>
|
||||||
<setting id="saftyMargin" type="slider" label="39051" default="30" option="int" range="10,1,300" visible="eq(-1,true)"/>
|
<setting id="saftyMargin" type="slider" label="39051" default="60" option="int" range="10,1,300" visible="eq(-1,true)"/>
|
||||||
<setting id="fullSyncInterval" type="number" label="39053" default="60" option="int" />
|
<setting id="fullSyncInterval" type="number" label="39053" default="60" option="int" />
|
||||||
|
<setting id="dbSyncScreensaver" type="bool" label="39062" default="false" /><!--Sync when screensaver is deactivated-->
|
||||||
|
|
||||||
<setting type="lsep" label="30538" /><!-- Complete Re-Sync necessary -->
|
<setting type="lsep" label="30538" /><!-- Complete Re-Sync necessary -->
|
||||||
|
<setting id="FanartTV" label="30539" type="bool" default="false" /><!-- Download additional art from FanArtTV -->
|
||||||
|
<setting id="setFanartTV" label="30540" type="bool" default="true" /><!-- Download movie set/collection art from FanArtTV -->
|
||||||
<setting id="enableMusic" type="bool" label="30509" default="true" />
|
<setting id="enableMusic" type="bool" label="30509" default="true" />
|
||||||
<setting id="useDirectPaths" type="enum" label="30511" values="Addon(Default)|Native(Direct paths)" default="0" visible="true"/> <!-- Playback mode -->
|
<setting id="useDirectPaths" type="enum" label="30511" values="Addon(Default)|Native(Direct paths)" default="0" visible="true"/> <!-- Playback mode -->
|
||||||
|
|
||||||
|
|
||||||
<setting id="streamMusic" type="bool" label="30510" default="false" visible="false" subsetting="true"/> <!-- Direct stream Music library -->
|
<setting id="streamMusic" type="bool" label="30510" default="false" visible="false" subsetting="true"/> <!-- Direct stream Music library -->
|
||||||
<setting type="lsep" label="30523" visible="false"/> <!-- Music metadata options -->
|
<setting type="lsep" label="30523" visible="false"/> <!-- Music metadata options -->
|
||||||
|
@ -66,7 +60,9 @@
|
||||||
<setting id="enableExportSongRating" type="bool" label="30525" default="false" visible="false" />
|
<setting id="enableExportSongRating" type="bool" label="30525" default="false" visible="false" />
|
||||||
<setting id="kodiplextimeoffset" type="number" label="Time difference in seconds (Koditime - Plextime)" default="0" visible="false" option="int" />
|
<setting id="kodiplextimeoffset" type="number" label="Time difference in seconds (Koditime - Plextime)" default="0" visible="false" option="int" />
|
||||||
<setting id="enableUpdateSongRating" type="bool" label="30526" default="false" visible="false" />
|
<setting id="enableUpdateSongRating" type="bool" label="30526" default="false" visible="false" />
|
||||||
<setting id="emby_pathverified" type="bool" default="false" visible="false" /> <!-- If 'false': one single warning message pops up if PKC cannot verify direct paths -->
|
<setting id="plex_pathverified" type="bool" default="false" visible="false" /> <!-- If 'false': one single warning message pops up if PKC cannot verify direct paths -->
|
||||||
|
<setting id="themoviedbAPIKey" type="text" default="ae06df54334aa653354e9a010f4b81cb" visible="false"/>
|
||||||
|
<setting id="FanArtTVAPIKey" type="text" default="639191cb0774661597f28a47e7e2bad5" visible="false"/>
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="39057"><!-- Customize Paths -->
|
<category label="39057"><!-- Customize Paths -->
|
||||||
|
@ -79,10 +75,14 @@
|
||||||
<setting id="remapSMBtvNew" type="text" label="39040" default="smb://" visible="eq(-4,true)"/> <!-- Replace Plex TV SHOWS with: -->
|
<setting id="remapSMBtvNew" type="text" label="39040" default="smb://" visible="eq(-4,true)"/> <!-- Replace Plex TV SHOWS with: -->
|
||||||
<setting id="remapSMBmusicOrg" type="text" label="39041" default="" visible="eq(-5,true)"/> <!-- Original Plex MUSIC path to replace: -->
|
<setting id="remapSMBmusicOrg" type="text" label="39041" default="" visible="eq(-5,true)"/> <!-- Original Plex MUSIC path to replace: -->
|
||||||
<setting id="remapSMBmusicNew" type="text" label="39042" default="smb://" visible="eq(-6,true)"/> <!-- Replace Plex MUSIC with: -->
|
<setting id="remapSMBmusicNew" type="text" label="39042" default="smb://" visible="eq(-6,true)"/> <!-- Replace Plex MUSIC with: -->
|
||||||
|
<setting id="remapSMBphotoOrg" type="text" label="39045" default="" visible="eq(-7,true)"/> <!-- Original Plex MUSIC path to replace: -->
|
||||||
|
<setting id="remapSMBphotoNew" type="text" label="39046" default="smb://" visible="eq(-8,true)"/> <!-- Replace Plex MUSIC with: -->
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="30516"><!-- Playback -->
|
<category label="30516"><!-- Playback -->
|
||||||
<setting type="sep" />
|
<setting type="sep" />
|
||||||
|
<setting id="bestQuality" type="bool" label="30541" default="false" />
|
||||||
|
<setting id="bestTrailer" type="bool" label="30542" default="true" />
|
||||||
<setting id="enableCinema" type="bool" label="30518" default="true" />
|
<setting id="enableCinema" type="bool" label="30518" default="true" />
|
||||||
<setting id="askCinema" type="bool" label="30519" default="false" visible="eq(-1,true)" subsetting="true" />
|
<setting id="askCinema" type="bool" label="30519" default="false" visible="eq(-1,true)" subsetting="true" />
|
||||||
<setting id="trailerNumber" type="slider" label="39000" default="3" visible="eq(-2,true)" range="1,1,15" option="int" />
|
<setting id="trailerNumber" type="slider" label="39000" default="3" visible="eq(-2,true)" range="1,1,15" option="int" />
|
||||||
|
@ -93,11 +93,12 @@
|
||||||
<setting id="resumeJumpBack" type="slider" label="30521" default="10" range="0,1,120" option="int" />
|
<setting id="resumeJumpBack" type="slider" label="30521" default="10" range="0,1,120" option="int" />
|
||||||
<setting type="sep" />
|
<setting type="sep" />
|
||||||
<setting id="playType" type="enum" label="30002" values="Direct Play (default)|Direct Stream|Force Transcode" default="0" />
|
<setting id="playType" type="enum" label="30002" values="Direct Play (default)|Direct Stream|Force Transcode" default="0" />
|
||||||
<setting id="transcoderVideoQualities" type="enum" label="30160" values="420x420, 320Kbps|576x320, 720Kbps|720x480, 1.5Mbps|1024x768, 2Mbps|1280x720, 3Mbps|1280x720, 4Mbps|1920x1080, 8Mbps|1920x1080, 10Mbps|1920x1080, 12Mbps|1920x1080, 20Mbps|1920x1080, 40Mbps" default="11" />
|
<setting id="transcoderVideoQualities" type="enum" label="30160" values="420x420, 320Kbps|576x320, 720Kbps|720x480, 1.5Mbps|1024x768, 2Mbps|1280x720, 3Mbps|1280x720, 4Mbps|1920x1080, 8Mbps|1920x1080, 10Mbps|1920x1080, 12Mbps|1920x1080, 20Mbps|1920x1080, 40Mbps" default="10" />
|
||||||
<setting id="transcodeH265" type="enum" label="30522" default="0" values="Disabled (default)|480p (and higher)|720p (and higher)|1080p" />
|
<setting id="transcodeH265" type="enum" label="30522" default="0" values="Disabled (default)|480p (and higher)|720p (and higher)|1080p" />
|
||||||
|
<setting id="transcodeHi10P" type="bool" label="39063" default="false"/>
|
||||||
|
<setting id="transcodeHEVC" type="bool" label="39065" default="false"/>
|
||||||
<setting id="audioBoost" type="slider" label="39001" default="0" range="0,10,100" option="int"/>
|
<setting id="audioBoost" type="slider" label="39001" default="0" range="0,10,100" option="int"/>
|
||||||
<setting id="subtitleSize" label="39002" type="slider" option="int" range="0,30,300" default="100" />
|
<setting id="subtitleSize" label="39002" type="slider" option="int" range="0,30,300" default="100" />
|
||||||
<setting id="markPlayed" type="number" visible="false" default="95" />
|
|
||||||
<setting id="failedCount" type="number" visible="false" default="0" />
|
<setting id="failedCount" type="number" visible="false" default="0" />
|
||||||
<setting id="networkCreds" type="text" visible="false" default="" />
|
<setting id="networkCreds" type="text" visible="false" default="" />
|
||||||
</category>
|
</category>
|
||||||
|
@ -125,12 +126,17 @@
|
||||||
|
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="39045"><!-- Appearance Tweaks -->
|
<category label="39073"><!-- Appearance Tweaks -->
|
||||||
<setting id="connectMsg" type="bool" label="30249" default="true" />
|
<setting id="connectMsg" type="bool" label="30249" default="true" />
|
||||||
<setting type="lsep" label="39046" />
|
<setting type="lsep" label="39074" /><!-- TV Shows -->
|
||||||
<setting id="OnDeckTVextended" type="bool" label="39058" default="true" /><!-- Extend Plex TV Series "On Deck" view to all shows -->
|
<setting id="OnDeckTVextended" type="bool" label="39058" default="true" /><!-- Extend Plex TV Series "On Deck" view to all shows -->
|
||||||
<setting id="OnDeckTvAppendShow" type="bool" label="39047" default="false" /><!--On Deck view: Append show title to episode-->
|
<setting id="OnDeckTvAppendShow" type="bool" label="39047" default="false" /><!--On Deck view: Append show title to episode-->
|
||||||
<setting id="OnDeckTvAppendSeason" type="bool" label="39048" default="false" /><!--On Deck view: Append season number to episode-->
|
<setting id="OnDeckTvAppendSeason" type="bool" label="39048" default="false" /><!--On Deck view: Append season number to episode-->
|
||||||
|
<setting id="TVShowWatched" type="bool" label="39064" default="true" /><!--Recently Added: Also show already watched episodes-->
|
||||||
|
<setting id="RecentTvAppendShow" type="bool" label="39059" default="false" /><!--Recently added: Append show title to episode-->
|
||||||
|
<setting id="RecentTvAppendSeason" type="bool" label="39060" default="false" /><!--Recently Added: Append season- and episode-number SxxExx-->
|
||||||
|
<setting type="lsep" label="30302" /><!-- Movies -->
|
||||||
|
<setting id="MovieShowWatched" type="bool" label="39066" default="true" /><!--Recently Added: Also show already watched episodes-->
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="30022"><!-- Advanced -->
|
<category label="30022"><!-- Advanced -->
|
||||||
|
|
130
service.py
130
service.py
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime
|
|
||||||
import Queue
|
import Queue
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
|
@ -14,19 +13,31 @@ import xbmcgui
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||||
addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
try:
|
||||||
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
||||||
|
except TypeError:
|
||||||
|
addon_path = _addon.getAddonInfo('path').decode()
|
||||||
|
try:
|
||||||
|
base_resource = xbmc.translatePath(os.path.join(
|
||||||
|
addon_path,
|
||||||
|
'resources',
|
||||||
|
'lib')).decode('utf-8')
|
||||||
|
except TypeError:
|
||||||
|
base_resource = xbmc.translatePath(os.path.join(
|
||||||
|
addon_path,
|
||||||
|
'resources',
|
||||||
|
'lib')).decode()
|
||||||
|
|
||||||
sys.path.append(base_resource)
|
sys.path.append(base_resource)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
import utils
|
||||||
import userclient
|
import userclient
|
||||||
import clientinfo
|
import clientinfo
|
||||||
import initialsetup
|
import initialsetup
|
||||||
import kodimonitor
|
import kodimonitor
|
||||||
import librarysync
|
import librarysync
|
||||||
import player
|
|
||||||
import utils
|
|
||||||
import videonodes
|
import videonodes
|
||||||
import websocket_client as wsc
|
import websocket_client as wsc
|
||||||
import downloadutils
|
import downloadutils
|
||||||
|
@ -59,11 +70,9 @@ class Service():
|
||||||
logLevel = self.getLogLevel()
|
logLevel = self.getLogLevel()
|
||||||
self.monitor = xbmc.Monitor()
|
self.monitor = xbmc.Monitor()
|
||||||
|
|
||||||
window('emby_logLevel', value=str(logLevel))
|
window('plex_logLevel', value=str(logLevel))
|
||||||
window('emby_kodiProfile', value=xbmc.translatePath("special://profile"))
|
window('plex_kodiProfile', value=xbmc.translatePath("special://profile"))
|
||||||
window('emby_pluginpath', value=utils.settings('useDirectPaths'))
|
window('plex_pluginpath', value=utils.settings('useDirectPaths'))
|
||||||
|
|
||||||
self.runPlexCompanion = utils.settings('plexCompanion')
|
|
||||||
|
|
||||||
# Initial logging
|
# Initial logging
|
||||||
log("======== START %s ========" % self.addonName, 0)
|
log("======== START %s ========" % self.addonName, 0)
|
||||||
|
@ -76,16 +85,17 @@ class Service():
|
||||||
# Reset window props for profile switch
|
# Reset window props for profile switch
|
||||||
properties = [
|
properties = [
|
||||||
|
|
||||||
"emby_online", "emby_serverStatus", "emby_onWake",
|
"plex_online", "plex_serverStatus", "plex_onWake",
|
||||||
"emby_syncRunning", "emby_dbCheck", "emby_kodiScan",
|
"plex_dbCheck", "plex_kodiScan",
|
||||||
"emby_shouldStop", "currUserId", "emby_dbScan", "emby_sessionId",
|
"plex_shouldStop", "currUserId", "plex_dbScan",
|
||||||
"emby_initialScan", "emby_customplaylist", "emby_playbackProps",
|
"plex_initialScan", "plex_customplaylist", "plex_playbackProps",
|
||||||
"plex_runLibScan", "plex_username", "pms_token", "plex_token",
|
"plex_runLibScan", "plex_username", "pms_token", "plex_token",
|
||||||
"pms_server", "plex_machineIdentifier", "plex_servername",
|
"pms_server", "plex_machineIdentifier", "plex_servername",
|
||||||
"plex_authenticated", "PlexUserImage", "useDirectPaths",
|
"plex_authenticated", "PlexUserImage", "useDirectPaths",
|
||||||
"replaceSMB", "remapSMB", "remapSMBmovieOrg", "remapSMBtvOrg",
|
"replaceSMB", "remapSMB", "remapSMBmovieOrg", "remapSMBtvOrg",
|
||||||
"remapSMBmusicOrg", "remapSMBmovieNew", "remapSMBtvNew",
|
"remapSMBmusicOrg", "remapSMBmovieNew", "remapSMBtvNew",
|
||||||
"remapSMBmusicNew", "suspend_LibraryThread", "plex_terminateNow",
|
"remapSMBmusicNew", "remapSMBphotoOrg", "remapSMBphotoNew",
|
||||||
|
"suspend_LibraryThread", "plex_terminateNow",
|
||||||
"kodiplextimeoffset", "countError", "countUnauthorized"
|
"kodiplextimeoffset", "countError", "countUnauthorized"
|
||||||
]
|
]
|
||||||
for prop in properties:
|
for prop in properties:
|
||||||
|
@ -93,9 +103,9 @@ class Service():
|
||||||
|
|
||||||
# Clear video nodes properties
|
# Clear video nodes properties
|
||||||
videonodes.VideoNodes().clearProperties()
|
videonodes.VideoNodes().clearProperties()
|
||||||
|
|
||||||
# Set the minimum database version
|
# Set the minimum database version
|
||||||
window('emby_minDBVersion', value="1.1.4")
|
window('plex_minDBVersion', value="1.1.5")
|
||||||
|
|
||||||
def getLogLevel(self):
|
def getLogLevel(self):
|
||||||
try:
|
try:
|
||||||
|
@ -127,61 +137,28 @@ class Service():
|
||||||
user = userclient.UserClient()
|
user = userclient.UserClient()
|
||||||
ws = wsc.WebSocket(queue)
|
ws = wsc.WebSocket(queue)
|
||||||
library = librarysync.LibrarySync(queue)
|
library = librarysync.LibrarySync(queue)
|
||||||
kplayer = player.Player()
|
|
||||||
xplayer = xbmc.Player()
|
|
||||||
plx = PlexAPI.PlexAPI()
|
plx = PlexAPI.PlexAPI()
|
||||||
|
|
||||||
# Sync and progress report
|
counter = 0
|
||||||
lastProgressUpdate = datetime.today()
|
|
||||||
|
|
||||||
while not monitor.abortRequested():
|
while not monitor.abortRequested():
|
||||||
|
|
||||||
if window('emby_kodiProfile') != kodiProfile:
|
if window('plex_kodiProfile') != kodiProfile:
|
||||||
# Profile change happened, terminate this thread and others
|
# Profile change happened, terminate this thread and others
|
||||||
log("Kodi profile was: %s and changed to: %s. Terminating old Emby thread."
|
log("Kodi profile was: %s and changed to: %s. Terminating old "
|
||||||
% (kodiProfile, utils.window('emby_kodiProfile')), 1)
|
"PlexKodiConnect thread."
|
||||||
|
% (kodiProfile, utils.window('plex_kodiProfile')), 1)
|
||||||
break
|
break
|
||||||
|
|
||||||
# Before proceeding, need to make sure:
|
# Before proceeding, need to make sure:
|
||||||
# 1. Server is online
|
# 1. Server is online
|
||||||
# 2. User is set
|
# 2. User is set
|
||||||
# 3. User has access to the server
|
# 3. User has access to the server
|
||||||
|
|
||||||
if window('emby_online') == "true":
|
if window('plex_online') == "true":
|
||||||
# Emby server is online
|
# Plex server is online
|
||||||
# Verify if user is set and has access to the server
|
# Verify if user is set and has access to the server
|
||||||
if (user.currUser is not None) and user.HasAccess:
|
if (user.currUser is not None) and user.HasAccess:
|
||||||
# If an item is playing
|
if not self.kodimonitor_running:
|
||||||
if xplayer.isPlaying():
|
|
||||||
try:
|
|
||||||
# Update and report progress
|
|
||||||
playtime = xplayer.getTime()
|
|
||||||
totalTime = xplayer.getTotalTime()
|
|
||||||
currentFile = kplayer.currentFile
|
|
||||||
|
|
||||||
# Update positionticks
|
|
||||||
if kplayer.played_info.get(currentFile) is not None:
|
|
||||||
kplayer.played_info[currentFile]['currentPosition'] = playtime
|
|
||||||
|
|
||||||
td = datetime.today() - lastProgressUpdate
|
|
||||||
secDiff = td.seconds
|
|
||||||
|
|
||||||
# Report progress to Emby server
|
|
||||||
if (secDiff > 3):
|
|
||||||
kplayer.reportPlayback()
|
|
||||||
lastProgressUpdate = datetime.today()
|
|
||||||
|
|
||||||
elif window('emby_command') == "true":
|
|
||||||
# Received a remote control command that
|
|
||||||
# requires updating immediately
|
|
||||||
window('emby_command', clear=True)
|
|
||||||
kplayer.reportPlayback()
|
|
||||||
lastProgressUpdate = datetime.today()
|
|
||||||
except Exception as e:
|
|
||||||
log("Exception in Playback Monitor Service: %s" % e, 1)
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Start up events
|
# Start up events
|
||||||
self.warn_auth = True
|
self.warn_auth = True
|
||||||
if connectMsg and self.welcome_msg:
|
if connectMsg and self.welcome_msg:
|
||||||
|
@ -194,8 +171,7 @@ class Service():
|
||||||
time=2000,
|
time=2000,
|
||||||
sound=False)
|
sound=False)
|
||||||
# Start monitoring kodi events
|
# Start monitoring kodi events
|
||||||
if not self.kodimonitor_running:
|
self.kodimonitor_running = kodimonitor.KodiMonitor()
|
||||||
self.kodimonitor_running = kodimonitor.KodiMonitor()
|
|
||||||
|
|
||||||
# Start the Websocket Client
|
# Start the Websocket Client
|
||||||
if not self.websocket_running:
|
if not self.websocket_running:
|
||||||
|
@ -206,8 +182,7 @@ class Service():
|
||||||
self.library_running = True
|
self.library_running = True
|
||||||
library.start()
|
library.start()
|
||||||
# Start the Plex Companion thread
|
# Start the Plex Companion thread
|
||||||
if not self.plexCompanion_running and \
|
if not self.plexCompanion_running:
|
||||||
self.runPlexCompanion == "true":
|
|
||||||
self.plexCompanion_running = True
|
self.plexCompanion_running = True
|
||||||
plexCompanion = PlexCompanion.PlexCompanion()
|
plexCompanion = PlexCompanion.PlexCompanion()
|
||||||
plexCompanion.start()
|
plexCompanion.start()
|
||||||
|
@ -224,7 +199,7 @@ class Service():
|
||||||
# Verify access with an API call
|
# Verify access with an API call
|
||||||
user.hasAccess()
|
user.hasAccess()
|
||||||
|
|
||||||
if window('emby_online') != "true":
|
if window('plex_online') != "true":
|
||||||
# Server went offline
|
# Server went offline
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -233,7 +208,7 @@ class Service():
|
||||||
break
|
break
|
||||||
xbmc.sleep(50)
|
xbmc.sleep(50)
|
||||||
else:
|
else:
|
||||||
# Wait until Emby server is online
|
# Wait until Plex server is online
|
||||||
# or Kodi is shut down.
|
# or Kodi is shut down.
|
||||||
while not monitor.abortRequested():
|
while not monitor.abortRequested():
|
||||||
server = user.getServer()
|
server = user.getServer()
|
||||||
|
@ -244,11 +219,10 @@ class Service():
|
||||||
# Server is offline or cannot be reached
|
# Server is offline or cannot be reached
|
||||||
# Alert the user and suppress future warning
|
# Alert the user and suppress future warning
|
||||||
if self.server_online:
|
if self.server_online:
|
||||||
log("Server is offline.", 1)
|
log("Server is offline.", -1)
|
||||||
window('emby_online', value="false")
|
window('plex_online', value="false")
|
||||||
# Suspend threads
|
# Suspend threads
|
||||||
window('suspend_LibraryThread', value='true')
|
window('suspend_LibraryThread', value='true')
|
||||||
|
|
||||||
xbmcgui.Dialog().notification(
|
xbmcgui.Dialog().notification(
|
||||||
heading=lang(33001),
|
heading=lang(33001),
|
||||||
message="%s %s"
|
message="%s %s"
|
||||||
|
@ -257,8 +231,17 @@ class Service():
|
||||||
"plexkodiconnect/icon.png",
|
"plexkodiconnect/icon.png",
|
||||||
sound=False)
|
sound=False)
|
||||||
self.server_online = False
|
self.server_online = False
|
||||||
|
counter += 1
|
||||||
|
# Periodically check if the IP changed, e.g. per minute
|
||||||
|
if counter > 30:
|
||||||
|
counter = 0
|
||||||
|
setup = initialsetup.InitialSetup()
|
||||||
|
tmp = setup.PickPMS()
|
||||||
|
if tmp is not None:
|
||||||
|
setup.WritePMStoSettings(tmp)
|
||||||
else:
|
else:
|
||||||
# Server is online
|
# Server is online
|
||||||
|
counter = 0
|
||||||
if not self.server_online:
|
if not self.server_online:
|
||||||
# Server was offline when Kodi started.
|
# Server was offline when Kodi started.
|
||||||
# Wait for server to be fully established.
|
# Wait for server to be fully established.
|
||||||
|
@ -271,11 +254,11 @@ class Service():
|
||||||
message=lang(33003),
|
message=lang(33003),
|
||||||
icon="special://home/addons/plugin.video."
|
icon="special://home/addons/plugin.video."
|
||||||
"plexkodiconnect/icon.png",
|
"plexkodiconnect/icon.png",
|
||||||
time=2000,
|
time=5000,
|
||||||
sound=False)
|
sound=False)
|
||||||
self.server_online = True
|
self.server_online = True
|
||||||
log("Server %s is online and ready." % server, 1)
|
log("Server %s is online and ready." % server, 1)
|
||||||
window('emby_online', value="true")
|
window('plex_online', value="true")
|
||||||
if window('plex_authenticated') == 'true':
|
if window('plex_authenticated') == 'true':
|
||||||
# Server got offline when we were authenticated.
|
# Server got offline when we were authenticated.
|
||||||
# Hence resume threads
|
# Hence resume threads
|
||||||
|
@ -288,12 +271,11 @@ class Service():
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if monitor.waitForAbort(1):
|
if monitor.waitForAbort(2):
|
||||||
# Abort was requested while waiting.
|
# Abort was requested while waiting.
|
||||||
break
|
break
|
||||||
xbmc.sleep(50)
|
|
||||||
|
|
||||||
if monitor.waitForAbort(1):
|
if monitor.waitForAbort(0.05):
|
||||||
# Abort was requested while waiting. We should exit
|
# Abort was requested while waiting. We should exit
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -335,6 +317,6 @@ delay = int(utils.settings('startupDelay'))
|
||||||
xbmc.log("Delaying Plex startup by: %s sec..." % delay)
|
xbmc.log("Delaying Plex startup by: %s sec..." % delay)
|
||||||
if delay and xbmc.Monitor().waitForAbort(delay):
|
if delay and xbmc.Monitor().waitForAbort(delay):
|
||||||
# Start the service
|
# Start the service
|
||||||
xbmc.log("Abort requested while waiting. Emby for kodi not started.")
|
xbmc.log("Abort requested while waiting. PKC not started.")
|
||||||
else:
|
else:
|
||||||
Service().ServiceEntryPoint()
|
Service().ServiceEntryPoint()
|
||||||
|
|
Loading…
Reference in a new issue