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.
|
||||
2. **Compatibility**: PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing.
|
||||
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.
|
||||
|
||||
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)
|
||||
[The Wiki will hopefully answer all your questions](https://github.com/croneter/PlexKodiConnect/wiki)
|
||||
|
||||
### 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.
|
||||
### Checkout the PKC Wiki
|
||||
The [Wiki can be found here](https://github.com/croneter/PlexKodiConnect/wiki) and will hopefully answer all your questions.
|
||||
|
||||
|
||||
**What does it do?**
|
||||
### What does PKC do?
|
||||
|
||||
With other addons for Kodi there are a couple of issues:
|
||||
- 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
|
||||
- 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!
|
||||
- You can browse your media full speed, e.g. images are cached
|
||||
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, images are cached
|
||||
- All other Kodi addons will be able to "see" your media, thinking it's normal Kodi stuff
|
||||
- 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)
|
||||
|
||||
|
||||
**What is currently supported?**
|
||||
|
||||
Currently these features are working:
|
||||
- Movies and Home Videos
|
||||
- TV Shows
|
||||
PKC currently provides the following features:
|
||||
- All Plex library types
|
||||
+ Movies and Home Videos
|
||||
+ TV Shows
|
||||
+ Music
|
||||
+ Pictures and Photos
|
||||
- Different PKC interface languages:
|
||||
+ English
|
||||
+ German
|
||||
+ More coming up
|
||||
- [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
|
||||
- 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)
|
||||
- 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
|
||||
- *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
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
- 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 and tell what language they are in. However, this is not the case if you use direct paths
|
||||
|
||||
*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
|
||||
- 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.
|
||||
|
||||
|
||||
**Known Bugs:**
|
||||
- Resume a video does not work yet with Plex Watch Later
|
||||
### Issues being worked on
|
||||
|
||||
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
|
||||
- Pictures
|
||||
- Music Videos
|
||||
- Automatic updates
|
||||
- Simultaneously connecting to several PMS
|
||||
- Deleting PMS items from Kodi
|
||||
- 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.
|
||||
|
||||
**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!!
|
||||
- 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"?>
|
||||
<addon id="plugin.video.plexkodiconnect"
|
||||
name="PlexKodiConnect"
|
||||
version="1.1.4"
|
||||
version="1.3.0"
|
||||
provider-name="croneter">
|
||||
<requires>
|
||||
<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
|
||||
(you will need to rescan your library)
|
||||
- Plex Watch Later available as a separate Video Node!
|
||||
|
|
|
@ -10,13 +10,26 @@ import xbmc
|
|||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
||||
addon_ = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||
addon_path = addon_.getAddonInfo('path').decode('utf-8')
|
||||
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
||||
|
||||
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||
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)
|
||||
|
||||
import artwork
|
||||
import utils
|
||||
import artwork
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
import librarysync
|
||||
|
@ -36,31 +49,31 @@ def logMsg(msg, lvl=1):
|
|||
#Kodi contextmenu item to configure the emby settings
|
||||
#for now used to set ratings but can later be used to sync individual items etc.
|
||||
if __name__ == '__main__':
|
||||
itemid = xbmc.getInfoLabel("ListItem.DBID").decode("utf-8")
|
||||
itemtype = xbmc.getInfoLabel("ListItem.DBTYPE").decode("utf-8")
|
||||
itemid = utils.tryDecode(xbmc.getInfoLabel("ListItem.DBID"))
|
||||
itemtype = utils.tryDecode(xbmc.getInfoLabel("ListItem.DBTYPE"))
|
||||
|
||||
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(artists)"): itemtype = "artist"
|
||||
if not itemtype and xbmc.getCondVisibility("Container.Content(songs)"): itemtype = "song"
|
||||
if not itemtype and xbmc.getCondVisibility("Container.Content(pictures)"): itemtype = "picture"
|
||||
|
||||
if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"):
|
||||
embyid = xbmc.getInfoLabel("ListItem.Property(embyid)")
|
||||
if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(plexid)"):
|
||||
plexid = xbmc.getInfoLabel("ListItem.Property(plexid)")
|
||||
else:
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
item = emby_db.getItem_byKodiId(itemid, itemtype)
|
||||
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:
|
||||
item = PF.GetPlexMetadata(embyid)
|
||||
if plexid:
|
||||
item = PF.GetPlexMetadata(plexid)
|
||||
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
|
||||
API = PlexAPI.API(item[0])
|
||||
userdata = API.getUserData()
|
||||
|
@ -98,15 +111,15 @@ if __name__ == '__main__':
|
|||
ret = xbmcgui.Dialog().select(header, options)
|
||||
if ret != -1:
|
||||
if options[ret] == utils.language(30402):
|
||||
emby.updateUserRating(embyid, deletelike=True)
|
||||
emby.updateUserRating(plexid, deletelike=True)
|
||||
if options[ret] == utils.language(30403):
|
||||
emby.updateUserRating(embyid, like=True)
|
||||
emby.updateUserRating(plexid, like=True)
|
||||
if options[ret] == utils.language(30404):
|
||||
emby.updateUserRating(embyid, like=False)
|
||||
emby.updateUserRating(plexid, like=False)
|
||||
if options[ret] == utils.language(30405):
|
||||
emby.updateUserRating(embyid, favourite=True)
|
||||
emby.updateUserRating(plexid, favourite=True)
|
||||
if options[ret] == utils.language(30406):
|
||||
emby.updateUserRating(embyid, favourite=False)
|
||||
emby.updateUserRating(plexid, favourite=False)
|
||||
if options[ret] == utils.language(30407):
|
||||
kodiconn = utils.kodiSQL('music')
|
||||
kodicursor = kodiconn.cursor()
|
||||
|
@ -121,7 +134,7 @@ if __name__ == '__main__':
|
|||
musicutils.updateRatingToFile(newvalue, API.getFilePath())
|
||||
if utils.settings('enableExportSongRating') == "true":
|
||||
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 = ?" ))
|
||||
kodicursor.execute(query, (newvalue,itemid,))
|
||||
kodiconn.commit()
|
||||
|
@ -139,15 +152,15 @@ if __name__ == '__main__':
|
|||
line1=("Delete file from Emby Server? This will "
|
||||
"also delete the file(s) from disk!"))
|
||||
if not resp:
|
||||
logMsg("User skipped deletion for: %s." % embyid, 1)
|
||||
logMsg("User skipped deletion for: %s." % plexid, 1)
|
||||
delete = False
|
||||
|
||||
if delete:
|
||||
import downloadutils
|
||||
doUtils = downloadutils.DownloadUtils()
|
||||
url = "{server}/emby/Items/%s?format=json" % embyid
|
||||
logMsg("Deleting request: %s" % embyid, 0)
|
||||
doUtils.downloadUrl(url, type="DELETE")
|
||||
url = "{server}/emby/Items/%s?format=json" % plexid
|
||||
logMsg("Deleting request: %s" % plexid, 0)
|
||||
doUtils.downloadUrl(url, action_type="DELETE")
|
||||
|
||||
'''if utils.settings('skipContextMenu') != "true":
|
||||
if xbmcgui.Dialog().yesno(
|
||||
|
@ -156,8 +169,7 @@ if __name__ == '__main__':
|
|||
"also delete the file(s) from disk!")):
|
||||
import downloadutils
|
||||
doUtils = downloadutils.DownloadUtils()
|
||||
url = "{server}/emby/Items/%s?format=json" % embyid
|
||||
doUtils.downloadUrl(url, type="DELETE")'''
|
||||
doUtils.downloadUrl("{server}/emby/Items/%s?format=json" % plexid, action_type="DELETE")'''
|
||||
|
||||
xbmc.sleep(500)
|
||||
xbmc.executebuiltin("Container.Update")
|
50
default.py
50
default.py
|
@ -10,11 +10,24 @@ import xbmc
|
|||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
addon_ = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||
addon_path = addon_.getAddonInfo('path').decode('utf-8')
|
||||
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
||||
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||
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)
|
||||
|
||||
###############################################################################
|
||||
|
@ -35,7 +48,6 @@ class Main:
|
|||
xbmc.log("PlexKodiConnect - Full sys.argv received: %s" % sys.argv)
|
||||
base_url = sys.argv[0]
|
||||
params = urlparse.parse_qs(sys.argv[2][1:])
|
||||
xbmc.log("PlexKodiConnect - Parameter string: %s" % sys.argv[2])
|
||||
try:
|
||||
mode = params['mode'][0]
|
||||
itemid = params.get('id', '')
|
||||
|
@ -67,18 +79,20 @@ class Main:
|
|||
'companion': entrypoint.plexCompanion,
|
||||
'switchuser': entrypoint.switchPlexUser,
|
||||
'deviceid': entrypoint.resetDeviceId,
|
||||
'reConnect': entrypoint.reConnect,
|
||||
'delete': entrypoint.deleteItem,
|
||||
'browseplex': entrypoint.BrowsePlexContent,
|
||||
'ondeck': entrypoint.getOnDeck,
|
||||
'chooseServer': entrypoint.chooseServer,
|
||||
'watchlater': entrypoint.watchlater
|
||||
'watchlater': entrypoint.watchlater,
|
||||
'enterPMS': entrypoint.enterPMS,
|
||||
'togglePlexTV': entrypoint.togglePlexTV,
|
||||
'playwatchlater': entrypoint.playWatchLater
|
||||
}
|
||||
|
||||
if "/extrafanart" in sys.argv[0]:
|
||||
embypath = sys.argv[2][1:]
|
||||
embyid = params.get('id',[""])[0]
|
||||
entrypoint.getExtraFanArt(embyid,embypath)
|
||||
plexpath = sys.argv[2][1:]
|
||||
plexid = params.get('id', [""])[0]
|
||||
entrypoint.getExtraFanArt(plexid, plexpath)
|
||||
|
||||
# Called by e.g. 3rd party plugin video extras
|
||||
if ("/Extras" in sys.argv[0] or "/VideoFiles" in sys.argv[0] or
|
||||
|
@ -121,6 +135,8 @@ class Main:
|
|||
modes[mode](itemid, folderid)
|
||||
elif mode == "companion":
|
||||
modes[mode](itemid, params=sys.argv[2])
|
||||
elif mode == 'playwatchlater':
|
||||
modes[mode](params.get('id')[0], params.get('viewOffset')[0])
|
||||
else:
|
||||
modes[mode]()
|
||||
else:
|
||||
|
@ -128,12 +144,14 @@ class Main:
|
|||
if mode == "settings":
|
||||
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||
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
|
||||
xbmcgui.Dialog().ok(heading="PlexKodiConnect",
|
||||
line1=("Unable to run the sync, the add-on is not "
|
||||
"connected to the Emby server."))
|
||||
utils.logMsg("PLEX", "Not connected to the emby server.", 1)
|
||||
xbmcgui.Dialog().ok(
|
||||
"PlexKodiConnect",
|
||||
"Unable to run the sync, the add-on is not connected "
|
||||
"to a Plex server.")
|
||||
utils.logMsg("PLEX",
|
||||
"Not connected to a PMS.", -1)
|
||||
return
|
||||
|
||||
else:
|
||||
|
@ -159,8 +177,8 @@ if ( __name__ == "__main__" ):
|
|||
import pstats
|
||||
import random
|
||||
from time import gmtime, strftime
|
||||
addonid = addon_.getAddonInfo('id').decode( 'utf-8' )
|
||||
datapath = os.path.join( xbmc.translatePath( "special://profile/" ).decode( 'utf-8' ), "addon_data", addonid )
|
||||
addonid = utils.tryDecode(addon_.getAddonInfo('id'))
|
||||
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" )
|
||||
cProfile.run( 'Main()', filename )
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<strings>
|
||||
<!-- Add-on settings -->
|
||||
<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="30005">Username: </string>
|
||||
<string id="30006">Password: </string>
|
||||
|
@ -131,7 +131,7 @@
|
|||
<string id="30157">Enable Enhanced Images (eg CoverArt)</string><!-- Verified -->
|
||||
<string id="30158">Metadata</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="30162">Add Season Number</string>
|
||||
|
@ -302,6 +302,10 @@
|
|||
<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="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 -->
|
||||
|
@ -328,7 +332,7 @@
|
|||
<string id="33020">Gathering tv shows from:</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="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="33025">completed in:</string>
|
||||
<string id="33026">Comparing movies from:</string>
|
||||
|
@ -338,7 +342,7 @@
|
|||
<string id="33030">Comparing episodes from:</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="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 -->
|
||||
<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="39022">local</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="39026">Enable constant background sync</string>
|
||||
<string id="39027">Playback Mode</string>
|
||||
|
@ -387,11 +392,10 @@
|
|||
<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="39044">Please enter your custom smb paths in the settings under "Sync Options" and then restart Kodi</string>
|
||||
|
||||
<string id="39045">Appearance Tweaks</string>
|
||||
<string id="39046">TV Shows</string>
|
||||
<string id="39045">Original Plex PHOTO path to replace:</string>
|
||||
<string id="39046">Replace Plex PHOTO with:</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="39050">[COLOR yellow]Choose Plex Server from a list[/COLOR]</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="39057">Customize Paths</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 -->
|
||||
<string id="39200">Log-out Plex Home User </string>
|
||||
|
@ -411,11 +431,20 @@
|
|||
<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="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="39208">Failed to reset PMS and plex.tv connects. Try to restart Kodi.</string>
|
||||
<string id="39209">[COLOR yellow]Log-in to plex.tv[/COLOR]</string>
|
||||
<string id="39207">Resetting PMS connections, please wait</string>
|
||||
<string id="39208">Failed to reset PKC. Try to restart Kodi.</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="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 -->
|
||||
|
|
|
@ -28,8 +28,10 @@
|
|||
<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="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="30015">Netzwerk</string>
|
||||
|
@ -164,7 +166,7 @@
|
|||
<string id="30157">Deaktiviere erweiterte Bilder (z.B. CoverArt)</string>
|
||||
<string id="30158">Metadaten</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="30162">Staffelnummer hinzufügen</string>
|
||||
|
@ -278,6 +280,8 @@
|
|||
<string id="30311">Musikstücke</string>
|
||||
<string id="30312">Kanäle</string>
|
||||
|
||||
<string id="33033">Kodi wird jetzt neu gestartet um die Änderungen anzuwenden.</string>
|
||||
|
||||
<!-- New to Plex -->
|
||||
<string id="39000">- Anzahl abzuspielender Trailer vor einem Film</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="39022">lokal</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="39026">Laufende Synchronisierung im Hintergrund aktivieren</string>
|
||||
<string id="39027">Playback Modus</string>
|
||||
|
@ -325,11 +330,10 @@
|
|||
<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="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">Erscheinung</string>
|
||||
<string id="39046">TV Serien</string>
|
||||
<string id="39045">Ursprünglicher Plex Pfad für PHOTOS:</string>
|
||||
<string id="39046">Plex PHOTO Pfade ersetzen durch:</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="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>
|
||||
|
@ -340,8 +344,22 @@
|
|||
<string id="39056">Verwendet für Synchronisierung sowie beim Versuch, Direct Play zu nutzen</string>
|
||||
<string id="39057">Pfade ändern</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 -->
|
||||
<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="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="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="39209">[COLOR yellow]Bei plex.tv einloggen[/COLOR]</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]plex.tv Login wechseln (ein- resp. ausloggen)[/COLOR]</string>
|
||||
<string id="39210">Noch nicht mit Plex Server verbunden</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 -->
|
||||
<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 traceback
|
||||
import socket
|
||||
import Queue
|
||||
|
||||
import xbmc
|
||||
|
||||
import utils
|
||||
from plexbmchelper import listener, plexgdm, subscribers, functions, \
|
||||
httppersist, settings
|
||||
from PlexFunctions import ParseContainerKey, GetPlayQueue, \
|
||||
ConvertPlexToKodiTime
|
||||
import playlist
|
||||
import player
|
||||
|
||||
|
||||
@utils.logging
|
||||
@utils.ThreadMethodsAdditionalSuspend('emby_serverStatus')
|
||||
@utils.ThreadMethodsAdditionalSuspend('plex_serverStatus')
|
||||
@utils.ThreadMethods
|
||||
class PlexCompanion(threading.Thread):
|
||||
"""
|
||||
Initialize with a Queue for callbacks
|
||||
"""
|
||||
def __init__(self):
|
||||
self.logMsg("----===## Starting PlexCompanion ##===----", 1)
|
||||
self.settings = settings.getSettings()
|
||||
|
@ -22,55 +30,150 @@ class PlexCompanion(threading.Thread):
|
|||
self.client = plexgdm.plexgdm()
|
||||
self.client.clientDetails(self.settings)
|
||||
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)
|
||||
|
||||
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):
|
||||
httpd = False
|
||||
# Cache for quicker while loops
|
||||
log = self.logMsg
|
||||
client = self.client
|
||||
threadStopped = self.threadStopped
|
||||
threadSuspended = self.threadSuspended
|
||||
start_count = 0
|
||||
|
||||
# Start up instances
|
||||
requestMgr = httppersist.RequestMgr()
|
||||
jsonClass = functions.jsonClass(requestMgr, self.settings)
|
||||
subscriptionManager = subscribers.SubscriptionManager(
|
||||
jsonClass, requestMgr)
|
||||
jsonClass, requestMgr, self.player, self.playlist)
|
||||
|
||||
# Start up httpd
|
||||
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)
|
||||
queue = Queue.Queue(maxsize=100)
|
||||
|
||||
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:
|
||||
log("Error: Unable to start web helper.", -1)
|
||||
httpd = False
|
||||
break
|
||||
xbmc.sleep(3000)
|
||||
|
||||
start_count += 1
|
||||
if start_count == 3:
|
||||
log("Error: Unable to start web helper.", -1)
|
||||
httpd = False
|
||||
break
|
||||
|
||||
if not httpd:
|
||||
return
|
||||
start_count += 1
|
||||
else:
|
||||
self.logMsg('User deactivated Plex Companion', 0)
|
||||
|
||||
client.start_all()
|
||||
|
||||
message_count = 0
|
||||
if httpd:
|
||||
t = threading.Thread(target=httpd.handle_request)
|
||||
|
||||
while not threadStopped():
|
||||
# If we are not authorized, sleep
|
||||
# Otherwise, we trigger a download which leads to a
|
||||
|
@ -80,34 +183,50 @@ class PlexCompanion(threading.Thread):
|
|||
break
|
||||
xbmc.sleep(1000)
|
||||
try:
|
||||
|
||||
httpd.handle_request()
|
||||
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 client.check_client_registration():
|
||||
log("Client is still registered", 1)
|
||||
else:
|
||||
log("Client is no longer registered", 1)
|
||||
log("Plex Companion still running on port %s"
|
||||
% self.settings['myport'], 1)
|
||||
message_count = 0
|
||||
if message_count == 3000:
|
||||
message_count = 0
|
||||
if client.check_client_registration():
|
||||
log("Client is still registered", 1)
|
||||
else:
|
||||
log("Client is no longer registered", 1)
|
||||
log("Plex Companion still running on port %s"
|
||||
% self.settings['myport'], 1)
|
||||
|
||||
# Get and set servers
|
||||
subscriptionManager.serverlist = client.getServerList()
|
||||
|
||||
subscriptionManager.notify()
|
||||
xbmc.sleep(50)
|
||||
if message_count % 30 == 0:
|
||||
subscriptionManager.serverlist = client.getServerList()
|
||||
subscriptionManager.notify()
|
||||
if not httpd:
|
||||
message_count = 0
|
||||
except:
|
||||
log("Error in loop, continuing anyway", 0)
|
||||
log("Error in loop, continuing anyway. Traceback:", 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()
|
||||
try:
|
||||
httpd.socket.shutdown(socket.SHUT_RDWR)
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
httpd.socket.close()
|
||||
if httpd:
|
||||
try:
|
||||
httpd.socket.shutdown(socket.SHUT_RDWR)
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
httpd.socket.close()
|
||||
log("----===## Plex Companion stopped ##===----", 0)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from urllib import urlencode
|
||||
from ast import literal_eval
|
||||
from urlparse import urlparse, parse_qs
|
||||
from urlparse import urlparse, parse_qsl
|
||||
import re
|
||||
from copy import deepcopy
|
||||
|
||||
|
@ -86,13 +86,13 @@ def GetPlexKeyNumber(plexKey):
|
|||
def ParseContainerKey(containerKey):
|
||||
"""
|
||||
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)
|
||||
library, key = GetPlexKeyNumber(result.path)
|
||||
query = parse_qs(result.query)
|
||||
query = dict(parse_qsl(result.query))
|
||||
return library, key, query
|
||||
|
||||
|
||||
|
@ -139,7 +139,7 @@ def SelectStreams(url, args):
|
|||
chosen.
|
||||
"""
|
||||
downloadutils.DownloadUtils().downloadUrl(
|
||||
url + '?' + urlencode(args), type='PUT')
|
||||
url + '?' + urlencode(args), action_type='PUT')
|
||||
|
||||
|
||||
def GetPlayQueue(playQueueID):
|
||||
|
@ -165,7 +165,7 @@ def GetPlexMetadata(key):
|
|||
Can be called with either Plex key '/library/metadata/xxxx'metadata
|
||||
OR with the digits 'xxxx' only.
|
||||
|
||||
Returns None if something went wrong
|
||||
Returns None or 401 if something went wrong
|
||||
"""
|
||||
key = str(key)
|
||||
if '/library/metadata/' in key:
|
||||
|
@ -173,14 +173,15 @@ def GetPlexMetadata(key):
|
|||
else:
|
||||
url = "{server}/library/metadata/" + key
|
||||
arguments = {
|
||||
'checkFiles': 1, # No idea
|
||||
'checkFiles': 0,
|
||||
'includeExtras': 1, # Trailers and Extras => Extras
|
||||
# 'includeRelated': 1, # Similar movies => Video -> Related
|
||||
# 'includeRelatedCount': 5,
|
||||
'includeReviews': 1,
|
||||
'includeRelated': 0, # Similar movies => Video -> Related
|
||||
# 'includeRelatedCount': 0,
|
||||
# 'includeOnDeck': 1,
|
||||
'includeChapters': 1,
|
||||
'includePopularLeaves': 1,
|
||||
'includeConcerts': 1
|
||||
# 'includeChapters': 1,
|
||||
# 'includePopularLeaves': 1,
|
||||
# 'includeConcerts': 1
|
||||
}
|
||||
url = url + '?' + urlencode(arguments)
|
||||
xml = downloadutils.DownloadUtils().downloadUrl(url)
|
||||
|
@ -388,7 +389,7 @@ def GetPlexPlaylist(itemid, librarySectionUUID, mediatype='movie'):
|
|||
'repeat': '0'
|
||||
}
|
||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||
url + '?' + urlencode(args), type="POST")
|
||||
url + '?' + urlencode(args), action_type="POST")
|
||||
try:
|
||||
xml[0].tag
|
||||
except (IndexError, TypeError, AttributeError):
|
||||
|
@ -424,14 +425,14 @@ def PMSHttpsEnabled(url):
|
|||
verifySSL=False)
|
||||
try:
|
||||
res.attrib
|
||||
except:
|
||||
except AttributeError:
|
||||
# Might have SSL deactivated. Try with http
|
||||
res = doUtils('http://%s/identity' % url,
|
||||
authenticate=False,
|
||||
verifySSL=False)
|
||||
try:
|
||||
res.attrib
|
||||
except:
|
||||
except AttributeError:
|
||||
logMsg(title, "Could not contact PMS %s" % url, -1)
|
||||
return None
|
||||
else:
|
||||
|
@ -448,16 +449,17 @@ def GetMachineIdentifier(url):
|
|||
|
||||
Returns None if something went wrong
|
||||
"""
|
||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||
url + '/identity', type="GET")
|
||||
xml = downloadutils.DownloadUtils().downloadUrl('%s/identity' % url,
|
||||
authenticate=False,
|
||||
verifySSL=False,
|
||||
timeout=4)
|
||||
try:
|
||||
xml.attrib
|
||||
except:
|
||||
machineIdentifier = xml.attrib['machineIdentifier']
|
||||
except (AttributeError, KeyError):
|
||||
logMsg(title, 'Could not get the PMS machineIdentifier for %s'
|
||||
% url, -1)
|
||||
return None
|
||||
machineIdentifier = xml.attrib.get('machineIdentifier')
|
||||
logMsg(title, 'Found machineIdentifier %s for %s'
|
||||
logMsg(title, 'Found machineIdentifier %s for the PMS %s'
|
||||
% (machineIdentifier, url), 1)
|
||||
return machineIdentifier
|
||||
|
||||
|
@ -480,7 +482,6 @@ def GetPMSStatus(token):
|
|||
answer = {}
|
||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||
'{server}/status/sessions',
|
||||
type="GET",
|
||||
headerOptions={'X-Plex-Token': token})
|
||||
try:
|
||||
xml.attrib
|
||||
|
@ -519,5 +520,5 @@ def scrobble(ratingKey, state):
|
|||
url = "{server}/:/unscrobble?" + urlencode(args)
|
||||
else:
|
||||
return
|
||||
downloadutils.DownloadUtils().downloadUrl(url, type="GET")
|
||||
downloadutils.DownloadUtils().downloadUrl(url)
|
||||
logMsg(title, "Toggled watched state for Plex item %s" % ratingKey, 1)
|
||||
|
|
|
@ -28,7 +28,7 @@ class Artwork():
|
|||
xbmc_port = None
|
||||
xbmc_username = None
|
||||
xbmc_password = None
|
||||
|
||||
|
||||
imageCacheThreads = []
|
||||
imageCacheLimitThreads = 0
|
||||
|
||||
|
@ -36,9 +36,9 @@ class Artwork():
|
|||
|
||||
self.enableTextureCache = utils.settings('enableTextureCache') == "true"
|
||||
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)
|
||||
|
||||
|
||||
if not self.xbmc_port and self.enableTextureCache:
|
||||
self.setKodiWebServerDetails()
|
||||
|
||||
|
@ -48,15 +48,15 @@ class Artwork():
|
|||
def double_urlencode(self, text):
|
||||
text = self.single_urlencode(text)
|
||||
text = self.single_urlencode(text)
|
||||
|
||||
|
||||
return 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:]
|
||||
|
||||
return text.decode("utf-8") #return the result again as unicode
|
||||
return utils.tryDecode(text) #return the result again as unicode
|
||||
|
||||
def setKodiWebServerDetails(self):
|
||||
# Get the Kodi webserver details - used to set the texture cache
|
||||
|
@ -74,9 +74,9 @@ class Artwork():
|
|||
result = json.loads(result)
|
||||
try:
|
||||
xbmc_webserver_enabled = result['result']['value']
|
||||
except KeyError, TypeError:
|
||||
except (KeyError, TypeError):
|
||||
xbmc_webserver_enabled = False
|
||||
|
||||
|
||||
if not xbmc_webserver_enabled:
|
||||
# Enable the webserver, it is disabled
|
||||
web_port = {
|
||||
|
@ -159,7 +159,7 @@ class Artwork():
|
|||
self.xbmc_password = result['result']['value']
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
|
||||
def FullTextureCacheSync(self):
|
||||
# This method will sync all Kodi artwork to textures13.db
|
||||
# and cache them locally. This takes diskspace!
|
||||
|
@ -167,30 +167,34 @@ class Artwork():
|
|||
string = xbmcaddon.Addon().getLocalizedString
|
||||
|
||||
if not xbmcgui.Dialog().yesno(
|
||||
"Image Texture Cache", string(39250).encode('utf-8')):
|
||||
"Image Texture Cache", string(39250)):
|
||||
return
|
||||
|
||||
|
||||
self.logMsg("Doing Image Cache Sync", 1)
|
||||
|
||||
|
||||
dialog = xbmcgui.DialogProgress()
|
||||
dialog.create("Emby for Kodi", "Image Cache Sync")
|
||||
|
||||
dialog.create("PlexKodiConnect", "Image Cache Sync")
|
||||
|
||||
# ask to rest all existing or not
|
||||
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)
|
||||
# Remove all existing textures first
|
||||
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
|
||||
path = utils.tryDecode(xbmc.translatePath("special://thumbnails/"))
|
||||
if utils.IfExists(path):
|
||||
allDirs, allFiles = xbmcvfs.listdir(path)
|
||||
for dir in allDirs:
|
||||
allDirs, allFiles = xbmcvfs.listdir(path+dir)
|
||||
for file in allFiles:
|
||||
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:
|
||||
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
|
||||
textureconnection = utils.kodiSQL('texture')
|
||||
texturecursor = textureconnection.cursor()
|
||||
|
@ -209,8 +213,8 @@ class Artwork():
|
|||
cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors
|
||||
result = cursor.fetchall()
|
||||
total = len(result)
|
||||
count = 1
|
||||
percentage = 0
|
||||
count = 1
|
||||
percentage = 0
|
||||
self.logMsg("Image cache sync about to process " + str(total) + " images", 1)
|
||||
for url in result:
|
||||
if dialog.iscanceled():
|
||||
|
@ -221,26 +225,26 @@ class Artwork():
|
|||
self.CacheTexture(url[0])
|
||||
count += 1
|
||||
cursor.close()
|
||||
|
||||
|
||||
# Cache all entries in music DB
|
||||
connection = utils.kodiSQL('music')
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT url FROM art")
|
||||
result = cursor.fetchall()
|
||||
total = len(result)
|
||||
count = 1
|
||||
count = 1
|
||||
percentage = 0
|
||||
self.logMsg("Image cache sync about to process " + str(total) + " images", 1)
|
||||
for url in result:
|
||||
if dialog.iscanceled():
|
||||
break
|
||||
break
|
||||
percentage = int((float(count) / float(total))*100)
|
||||
textMessage = str(count) + " of " + str(total)
|
||||
dialog.update(percentage, "Updating Image Cache: " + textMessage)
|
||||
self.CacheTexture(url[0])
|
||||
count += 1
|
||||
cursor.close()
|
||||
|
||||
|
||||
dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads)))
|
||||
self.logMsg("Waiting for all threads to exit", 1)
|
||||
while len(self.imageCacheThreads) > 0:
|
||||
|
@ -250,16 +254,16 @@ class Artwork():
|
|||
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)
|
||||
xbmc.sleep(500)
|
||||
|
||||
|
||||
dialog.close()
|
||||
|
||||
def addWorkerImageCacheThread(self, urlToAdd):
|
||||
|
||||
|
||||
while(True):
|
||||
# removed finished
|
||||
for thread in self.imageCacheThreads:
|
||||
if thread.isFinished:
|
||||
self.imageCacheThreads.remove(thread)
|
||||
self.imageCacheThreads.remove(thread)
|
||||
|
||||
# add a new thread or wait and retry if we hit our limit
|
||||
if(len(self.imageCacheThreads) < self.imageCacheLimitThreads):
|
||||
|
@ -273,14 +277,14 @@ class Artwork():
|
|||
else:
|
||||
self.logMsg("Waiting for empty queue spot: " + str(len(self.imageCacheThreads)), 2)
|
||||
xbmc.sleep(50)
|
||||
|
||||
|
||||
|
||||
|
||||
def CacheTexture(self, url):
|
||||
# Cache a single image url to the texture cache
|
||||
if url and self.enableTextureCache:
|
||||
if(self.imageCacheLimitThreads == 0 or self.imageCacheLimitThreads == None):
|
||||
#Add image to texture cache by simply calling it at the http endpoint
|
||||
|
||||
|
||||
url = self.double_urlencode(url)
|
||||
try: # Extreme short timeouts so we will have a exception.
|
||||
response = requests.head(
|
||||
|
@ -291,7 +295,7 @@ class Artwork():
|
|||
timeout=(0.01, 0.01))
|
||||
# We don't need the result
|
||||
except: pass
|
||||
|
||||
|
||||
else:
|
||||
self.addWorkerImageCacheThread(url)
|
||||
|
||||
|
@ -349,13 +353,13 @@ class Artwork():
|
|||
mediaType=mediaType,
|
||||
imageType="%s%s" % ("fanart", index),
|
||||
cursor=cursor)
|
||||
|
||||
|
||||
if backdropsNumber > 1:
|
||||
try: # Will only fail on the first try, str to int.
|
||||
index += 1
|
||||
except TypeError:
|
||||
index = 1
|
||||
|
||||
|
||||
elif art == "Primary":
|
||||
# Primary art is processed as thumb and poster for Kodi.
|
||||
for artType in kodiart[art]:
|
||||
|
@ -365,7 +369,7 @@ class Artwork():
|
|||
mediaType=mediaType,
|
||||
imageType=artType,
|
||||
cursor=cursor)
|
||||
|
||||
|
||||
elif kodiart.get(art):
|
||||
# Process the rest artwork type that Kodi can use
|
||||
self.addOrUpdateArt(
|
||||
|
@ -391,9 +395,11 @@ class Artwork():
|
|||
cursor.execute(query, (kodiId, mediaType, imageType,))
|
||||
try: # Update the artwork
|
||||
url = cursor.fetchone()[0]
|
||||
|
||||
|
||||
except TypeError: # Add the artwork
|
||||
cacheimage = True
|
||||
self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2)
|
||||
|
||||
query = (
|
||||
'''
|
||||
INSERT INTO art(media_id, media_type, type, url)
|
||||
|
@ -402,17 +408,21 @@ class Artwork():
|
|||
'''
|
||||
)
|
||||
cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
|
||||
|
||||
|
||||
else: # Only cache artwork if it changed
|
||||
if url != imageUrl:
|
||||
cacheimage = True
|
||||
|
||||
|
||||
# Only for the main backdrop, poster
|
||||
if (utils.window('emby_initialScan') != "true" and
|
||||
if (utils.window('plex_initialScan') != "true" and
|
||||
imageType in ("fanart", "poster")):
|
||||
# Delete current entry before updating with the new one
|
||||
self.deleteCachedArtwork(url)
|
||||
|
||||
self.logMsg(
|
||||
"Updating Art url for %s kodiId: %s (%s) -> (%s)"
|
||||
% (imageType, kodiId, url, imageUrl), 1)
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"UPDATE art",
|
||||
|
@ -422,9 +432,9 @@ class Artwork():
|
|||
"AND type = ?"
|
||||
))
|
||||
cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
|
||||
|
||||
|
||||
# Cache fanart and poster in Kodi texture cache
|
||||
if cacheimage and imageType in ("fanart", "poster", "thumb"):
|
||||
if cacheimage:
|
||||
self.CacheTexture(imageUrl)
|
||||
|
||||
def deleteArtwork(self, kodiid, mediatype, cursor):
|
||||
|
@ -453,24 +463,25 @@ class Artwork():
|
|||
try:
|
||||
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
|
||||
cachedurl = cursor.fetchone()[0]
|
||||
|
||||
|
||||
except TypeError:
|
||||
self.logMsg("Could not find cached url.", 1)
|
||||
|
||||
except OperationalError:
|
||||
self.logMsg("Database is locked. Skip deletion process.", 1)
|
||||
|
||||
|
||||
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)
|
||||
xbmcvfs.delete(thumbnails)
|
||||
|
||||
|
||||
try:
|
||||
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
|
||||
connection.commit()
|
||||
except OperationalError:
|
||||
self.logMsg("Issue deleting url from cache. Skipping.", 2)
|
||||
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
|
||||
|
@ -487,7 +498,7 @@ class Artwork():
|
|||
"%s/emby/Items/%s/Images/Primary?"
|
||||
"MaxWidth=400&MaxHeight=400&Index=0&Tag=%s"
|
||||
% (self.server, personId, tag))
|
||||
|
||||
|
||||
person['imageurl'] = image
|
||||
|
||||
return people
|
||||
|
@ -501,8 +512,6 @@ class Artwork():
|
|||
|
||||
def getAllArtwork(self, item, parentInfo=False):
|
||||
|
||||
server = self.server
|
||||
|
||||
itemid = item['Id']
|
||||
artworks = item['ImageTags']
|
||||
backdrops = item.get('BackdropImageTags',[])
|
||||
|
@ -527,13 +536,13 @@ def getAllArtwork(self, item, parentInfo=False):
|
|||
'Disc': "",
|
||||
'Backdrop': []
|
||||
}
|
||||
|
||||
|
||||
# Process backdrops
|
||||
for index, tag in enumerate(backdrops):
|
||||
artwork = (
|
||||
"%s/emby/Items/%s/Images/Backdrop/%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)
|
||||
|
||||
# Process the rest of the artwork
|
||||
|
@ -544,15 +553,15 @@ def getAllArtwork(self, item, parentInfo=False):
|
|||
artwork = (
|
||||
"%s/emby/Items/%s/Images/%s/0?"
|
||||
"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
|
||||
|
||||
# Process parent items if the main item is missing artwork
|
||||
if parentInfo:
|
||||
|
||||
|
||||
# Process parent backdrops
|
||||
if not allartworks['Backdrop']:
|
||||
|
||||
|
||||
parentId = item.get('ParentBackdropItemId')
|
||||
if parentId:
|
||||
# If there is a parentId, go through the parent backdrop list
|
||||
|
@ -562,7 +571,7 @@ def getAllArtwork(self, item, parentInfo=False):
|
|||
artwork = (
|
||||
"%s/emby/Items/%s/Images/Backdrop/%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)
|
||||
|
||||
# Process the rest of the artwork
|
||||
|
@ -570,15 +579,15 @@ def getAllArtwork(self, item, parentInfo=False):
|
|||
for parentart in parentartwork:
|
||||
|
||||
if not allartworks[parentart]:
|
||||
|
||||
|
||||
parentId = item.get('Parent%sItemId' % parentart)
|
||||
if parentId:
|
||||
|
||||
|
||||
parentTag = item['Parent%sImageTag' % parentart]
|
||||
artwork = (
|
||||
"%s/emby/Items/%s/Images/%s/0?"
|
||||
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
||||
% (server, parentId, parentart,
|
||||
% (self.server, parentId, parentart,
|
||||
maxWidth, maxHeight, parentTag, customquery))
|
||||
allartworks[parentart] = artwork
|
||||
|
||||
|
@ -587,12 +596,12 @@ def getAllArtwork(self, item, parentInfo=False):
|
|||
|
||||
parentId = item.get('AlbumId')
|
||||
if parentId and item.get('AlbumPrimaryImageTag'):
|
||||
|
||||
|
||||
parentTag = item['AlbumPrimaryImageTag']
|
||||
artwork = (
|
||||
"%s/emby/Items/%s/Images/Primary/0?"
|
||||
"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
|
||||
|
||||
return allartworks
|
||||
return allartworks
|
||||
|
|
|
@ -7,7 +7,7 @@ from uuid import uuid4
|
|||
import xbmc
|
||||
import xbmcaddon
|
||||
|
||||
from utils import logging, window, settings
|
||||
from utils import logging, window, settings, tryDecode
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -67,8 +67,7 @@ class ClientInfo():
|
|||
def getDeviceName(self):
|
||||
if settings('deviceNameOpt') == "false":
|
||||
# Use Kodi's deviceName
|
||||
deviceName = xbmc.getInfoLabel(
|
||||
'System.FriendlyName').decode('utf-8')
|
||||
deviceName = tryDecode(xbmc.getInfoLabel('System.FriendlyName'))
|
||||
else:
|
||||
deviceName = settings('deviceName')
|
||||
deviceName = deviceName.replace("\"", "_")
|
||||
|
|
|
@ -21,7 +21,7 @@ requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
|
|||
|
||||
|
||||
class ConnectUtils():
|
||||
|
||||
|
||||
# Borg - multiple instances, shared state
|
||||
_shared_state = {}
|
||||
clientInfo = clientinfo.ClientInfo()
|
||||
|
@ -60,8 +60,6 @@ class ConnectUtils():
|
|||
|
||||
def startSession(self):
|
||||
|
||||
log = self.logMsg
|
||||
|
||||
self.deviceId = self.clientInfo.getDeviceId()
|
||||
|
||||
# User is identified from this point
|
||||
|
@ -75,8 +73,8 @@ class ConnectUtils():
|
|||
if self.sslclient is not None:
|
||||
verify = self.sslclient
|
||||
except:
|
||||
log("Could not load SSL settings.", 1)
|
||||
|
||||
self.logMsg("Could not load SSL settings.", 1)
|
||||
|
||||
# Start session
|
||||
self.c = requests.Session()
|
||||
self.c.headers = header
|
||||
|
@ -85,7 +83,7 @@ class ConnectUtils():
|
|||
self.c.mount("http://", 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):
|
||||
try:
|
||||
|
@ -95,8 +93,7 @@ class ConnectUtils():
|
|||
|
||||
def getHeader(self, authenticate=True):
|
||||
|
||||
clientInfo = self.clientInfo
|
||||
version = clientInfo.getVersion()
|
||||
version = self.clientInfo.getVersion()
|
||||
|
||||
if not authenticate:
|
||||
# If user is not authenticated
|
||||
|
@ -105,9 +102,9 @@ class ConnectUtils():
|
|||
'X-Application': "Kodi/%s" % version,
|
||||
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Accept': "application/json"
|
||||
}
|
||||
}
|
||||
self.logMsg("Header: %s" % header, 1)
|
||||
|
||||
|
||||
else:
|
||||
token = self.token
|
||||
# Attached to the requests session
|
||||
|
@ -117,18 +114,17 @@ class ConnectUtils():
|
|||
'Accept': "application/json",
|
||||
'X-Application': "Kodi/%s" % version,
|
||||
'X-Connect-UserToken': token
|
||||
}
|
||||
}
|
||||
self.logMsg("Header: %s" % header, 1)
|
||||
|
||||
|
||||
return header
|
||||
|
||||
def doUrl(self, url, data=None, postBody=None, rtype="GET",
|
||||
parameters=None, authenticate=True, timeout=None):
|
||||
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
|
||||
log("=== ENTER connectUrl ===", 2)
|
||||
self.logMsg("=== ENTER connectUrl ===", 2)
|
||||
default_link = ""
|
||||
if timeout is None:
|
||||
timeout = self.timeout
|
||||
|
@ -137,7 +133,7 @@ class ConnectUtils():
|
|||
try:
|
||||
# If connect user is authenticated
|
||||
if authenticate:
|
||||
try:
|
||||
try:
|
||||
c = self.c
|
||||
# Replace for the real values
|
||||
url = url.replace("{server}", self.server)
|
||||
|
@ -167,7 +163,7 @@ class ConnectUtils():
|
|||
verifyssl = self.sslclient
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
# Prepare request
|
||||
if rtype == "GET":
|
||||
r = requests.get(url,
|
||||
|
@ -195,7 +191,7 @@ class ConnectUtils():
|
|||
verifyssl = self.sslclient
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
# Prepare request
|
||||
if rtype == "GET":
|
||||
r = requests.get(url,
|
||||
|
@ -213,28 +209,28 @@ class ConnectUtils():
|
|||
verify=verifyssl)
|
||||
|
||||
##### THE RESPONSE #####
|
||||
log(r.url, 1)
|
||||
log(r, 1)
|
||||
self.logMsg(r.url, 1)
|
||||
self.logMsg(r, 1)
|
||||
|
||||
if r.status_code == 204:
|
||||
# No body in the response
|
||||
log("====== 204 Success ======", 1)
|
||||
self.logMsg("====== 204 Success ======", 1)
|
||||
|
||||
elif r.status_code == requests.codes.ok:
|
||||
|
||||
try:
|
||||
|
||||
try:
|
||||
# UNICODE - JSON object
|
||||
r = r.json()
|
||||
log("====== 200 Success ======", 1)
|
||||
log("Response: %s" % r, 1)
|
||||
self.logMsg("====== 200 Success ======", 1)
|
||||
self.logMsg("Response: %s" % r, 1)
|
||||
return r
|
||||
|
||||
except:
|
||||
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:
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
##### EXCEPTIONS #####
|
||||
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
|
@ -242,8 +238,8 @@ class ConnectUtils():
|
|||
pass
|
||||
|
||||
except requests.exceptions.ConnectTimeout as e:
|
||||
log("Server timeout at: %s" % url, 0)
|
||||
log(e, 1)
|
||||
self.logMsg("Server timeout at: %s" % url, 0)
|
||||
self.logMsg(e, 1)
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
|
||||
|
@ -259,11 +255,11 @@ class ConnectUtils():
|
|||
pass
|
||||
|
||||
except requests.exceptions.SSLError as e:
|
||||
log("Invalid SSL certificate for: %s" % url, 0)
|
||||
log(e, 1)
|
||||
self.logMsg("Invalid SSL certificate for: %s" % url, 0)
|
||||
self.logMsg(e, 1)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
log("Unknown error connecting to: %s" % url, 0)
|
||||
log(e, 1)
|
||||
self.logMsg("Unknown error connecting to: %s" % url, 0)
|
||||
self.logMsg(e, 1)
|
||||
|
||||
return default_link
|
||||
|
|
|
@ -30,8 +30,6 @@ class DownloadUtils():
|
|||
# Borg - multiple instances, shared state
|
||||
_shared_state = {}
|
||||
|
||||
# Requests session
|
||||
timeout = 30
|
||||
# How many failed attempts before declaring PMS dead?
|
||||
connectionAttempts = 2
|
||||
# How many 401 returns before declaring unauthorized?
|
||||
|
@ -39,6 +37,8 @@ class DownloadUtils():
|
|||
|
||||
def __init__(self):
|
||||
self.__dict__ = self._shared_state
|
||||
# Requests session
|
||||
self.timeout = 30.0
|
||||
|
||||
def setUsername(self, username):
|
||||
"""
|
||||
|
@ -142,21 +142,22 @@ class DownloadUtils():
|
|||
header.update(options)
|
||||
return header
|
||||
|
||||
def __doDownload(self, s, type, **kwargs):
|
||||
if type == "GET":
|
||||
def __doDownload(self, s, action_type, **kwargs):
|
||||
if action_type == "GET":
|
||||
r = s.get(**kwargs)
|
||||
elif type == "POST":
|
||||
elif action_type == "POST":
|
||||
r = s.post(**kwargs)
|
||||
elif type == "DELETE":
|
||||
elif action_type == "DELETE":
|
||||
r = s.delete(**kwargs)
|
||||
elif type == "OPTIONS":
|
||||
elif action_type == "OPTIONS":
|
||||
r = s.options(**kwargs)
|
||||
elif type == "PUT":
|
||||
elif action_type == "PUT":
|
||||
r = s.put(**kwargs)
|
||||
return r
|
||||
|
||||
def downloadUrl(self, url, type="GET", postBody=None, parameters=None,
|
||||
authenticate=True, headerOptions=None, verifySSL=True):
|
||||
def downloadUrl(self, url, action_type="GET", postBody=None,
|
||||
parameters=None, authenticate=True, headerOptions=None,
|
||||
verifySSL=True, timeout=None):
|
||||
"""
|
||||
Override SSL check with verifySSL=False
|
||||
|
||||
|
@ -164,14 +165,14 @@ class DownloadUtils():
|
|||
Otherwise, 'empty' request will be made
|
||||
|
||||
Returns:
|
||||
False If an error occured
|
||||
None If an error occured
|
||||
True If connection worked but no body was received
|
||||
401, ... integer if PMS answered with HTTP error 401
|
||||
(unauthorized) or other http error codes
|
||||
xml xml etree root object, if applicable
|
||||
JSON json() object, if applicable
|
||||
"""
|
||||
kwargs = {}
|
||||
kwargs = {'timeout': self.timeout}
|
||||
if authenticate is True:
|
||||
# Get requests session
|
||||
try:
|
||||
|
@ -187,7 +188,6 @@ class DownloadUtils():
|
|||
# plex.tv and to check for PMS servers
|
||||
s = requests
|
||||
headerOptions = self.getHeader(options=headerOptions)
|
||||
kwargs['timeout'] = self.timeout
|
||||
if settings('sslcert') != 'None':
|
||||
kwargs['cert'] = settings('sslcert')
|
||||
|
||||
|
@ -202,10 +202,12 @@ class DownloadUtils():
|
|||
kwargs['data'] = postBody
|
||||
if parameters is not None:
|
||||
kwargs['params'] = parameters
|
||||
if timeout is not None:
|
||||
kwargs['timeout'] = timeout
|
||||
|
||||
# ACTUAL DOWNLOAD HAPPENING HERE
|
||||
try:
|
||||
r = self.__doDownload(s, type, **kwargs)
|
||||
r = self.__doDownload(s, action_type, **kwargs)
|
||||
|
||||
# THE EXCEPTIONS
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
|
@ -252,6 +254,9 @@ class DownloadUtils():
|
|||
|
||||
if r.status_code == 204:
|
||||
# No body in the response
|
||||
# But read (empty) content to release connection back to pool
|
||||
# (see requests: keep-alive documentation)
|
||||
r.content
|
||||
return True
|
||||
|
||||
elif r.status_code == 401:
|
||||
|
@ -269,11 +274,11 @@ class DownloadUtils():
|
|||
self.unauthorizedAttempts):
|
||||
self.logMsg('We seem to be truly unauthorized for PMS'
|
||||
' %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.
|
||||
self.logMsg('Setting PMS server status to '
|
||||
'unauthorized', 0)
|
||||
window('emby_serverStatus', value="401")
|
||||
window('plex_serverStatus', value="401")
|
||||
xbmcgui.Dialog().notification(
|
||||
self.addonName,
|
||||
"Unauthorized for PMS",
|
||||
|
@ -328,7 +333,7 @@ class DownloadUtils():
|
|||
if int(window('countError')) >= self.connectionAttempts:
|
||||
self.logMsg('Failed to connect to %s too many times. '
|
||||
'Declare PMS dead' % url, -1)
|
||||
window('emby_online', value="false")
|
||||
window('plex_online', value="false")
|
||||
except:
|
||||
# 'countError' not yet set
|
||||
pass
|
||||
|
|
|
@ -34,7 +34,6 @@ class Embydb_Functions():
|
|||
|
||||
def getViews(self):
|
||||
|
||||
embycursor = self.embycursor
|
||||
views = []
|
||||
|
||||
query = ' '.join((
|
||||
|
@ -42,8 +41,8 @@ class Embydb_Functions():
|
|||
"SELECT view_id",
|
||||
"FROM view"
|
||||
))
|
||||
embycursor.execute(query)
|
||||
rows = embycursor.fetchall()
|
||||
self.embycursor.execute(query)
|
||||
rows = self.embycursor.fetchall()
|
||||
for row in rows:
|
||||
views.append(row[0])
|
||||
return views
|
||||
|
@ -68,7 +67,6 @@ class Embydb_Functions():
|
|||
|
||||
def getView_byId(self, viewid):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -76,13 +74,13 @@ class Embydb_Functions():
|
|||
"FROM view",
|
||||
"WHERE view_id = ?"
|
||||
))
|
||||
embycursor.execute(query, (viewid,))
|
||||
view = embycursor.fetchone()
|
||||
self.embycursor.execute(query, (viewid,))
|
||||
view = self.embycursor.fetchone()
|
||||
|
||||
return view
|
||||
|
||||
def getView_byType(self, mediatype):
|
||||
|
||||
embycursor = self.embycursor
|
||||
views = []
|
||||
|
||||
query = ' '.join((
|
||||
|
@ -91,8 +89,8 @@ class Embydb_Functions():
|
|||
"FROM view",
|
||||
"WHERE media_type = ?"
|
||||
))
|
||||
embycursor.execute(query, (mediatype,))
|
||||
rows = embycursor.fetchall()
|
||||
self.embycursor.execute(query, (mediatype,))
|
||||
rows = self.embycursor.fetchall()
|
||||
for row in rows:
|
||||
views.append({
|
||||
|
||||
|
@ -105,23 +103,22 @@ class Embydb_Functions():
|
|||
|
||||
def getView_byName(self, tagname):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_id",
|
||||
"FROM view",
|
||||
"WHERE view_name = ?"
|
||||
))
|
||||
embycursor.execute(query, (tagname,))
|
||||
self.embycursor.execute(query, (tagname,))
|
||||
try:
|
||||
view = embycursor.fetchone()[0]
|
||||
view = self.embycursor.fetchone()[0]
|
||||
|
||||
except TypeError:
|
||||
view = None
|
||||
|
||||
return view
|
||||
|
||||
def addView(self, embyid, name, mediatype, tagid):
|
||||
def addView(self, plexid, name, mediatype, tagid):
|
||||
|
||||
query = (
|
||||
'''
|
||||
|
@ -131,7 +128,7 @@ class Embydb_Functions():
|
|||
VALUES (?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
self.embycursor.execute(query, (embyid, name, mediatype, tagid))
|
||||
self.embycursor.execute(query, (plexid, name, mediatype, tagid))
|
||||
|
||||
def updateView(self, name, tagid, mediafolderid):
|
||||
|
||||
|
@ -188,9 +185,7 @@ class Embydb_Functions():
|
|||
except:
|
||||
return None
|
||||
|
||||
def getItem_byId(self, embyid):
|
||||
|
||||
embycursor = self.embycursor
|
||||
def getItem_byId(self, plexid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -199,14 +194,12 @@ class Embydb_Functions():
|
|||
"WHERE emby_id = ?"
|
||||
))
|
||||
try:
|
||||
embycursor.execute(query, (embyid,))
|
||||
item = embycursor.fetchone()
|
||||
self.embycursor.execute(query, (plexid,))
|
||||
item = self.embycursor.fetchone()
|
||||
return item
|
||||
except: return None
|
||||
|
||||
def getItem_byWildId(self, embyid):
|
||||
|
||||
embycursor = self.embycursor
|
||||
def getItem_byWildId(self, plexid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -214,25 +207,19 @@ class Embydb_Functions():
|
|||
"FROM emby",
|
||||
"WHERE emby_id LIKE ?"
|
||||
))
|
||||
embycursor.execute(query, (embyid+"%",))
|
||||
items = embycursor.fetchall()
|
||||
|
||||
return items
|
||||
self.embycursor.execute(query, (plexid+"%",))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getItem_byView(self, mediafolderid):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT kodi_id",
|
||||
"FROM emby",
|
||||
"WHERE media_folder = ?"
|
||||
))
|
||||
embycursor.execute(query, (mediafolderid,))
|
||||
items = embycursor.fetchall()
|
||||
|
||||
return items
|
||||
self.embycursor.execute(query, (mediafolderid,))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getPlexId(self, kodiid, mediatype):
|
||||
"""
|
||||
|
@ -253,8 +240,6 @@ class Embydb_Functions():
|
|||
|
||||
def getItem_byKodiId(self, kodiid, mediatype):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, parent_id",
|
||||
|
@ -262,15 +247,11 @@ class Embydb_Functions():
|
|||
"WHERE kodi_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
embycursor.execute(query, (kodiid, mediatype,))
|
||||
item = embycursor.fetchone()
|
||||
|
||||
return item
|
||||
self.embycursor.execute(query, (kodiid, mediatype,))
|
||||
return self.embycursor.fetchone()
|
||||
|
||||
def getItem_byParentId(self, parentid, mediatype):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, kodi_id, kodi_fileid",
|
||||
|
@ -278,15 +259,11 @@ class Embydb_Functions():
|
|||
"WHERE parent_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
embycursor.execute(query, (parentid, mediatype,))
|
||||
items = embycursor.fetchall()
|
||||
|
||||
return items
|
||||
self.embycursor.execute(query, (parentid, mediatype,))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getItemId_byParentId(self, parentid, mediatype):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, kodi_id",
|
||||
|
@ -294,29 +271,21 @@ class Embydb_Functions():
|
|||
"WHERE parent_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
embycursor.execute(query, (parentid, mediatype,))
|
||||
items = embycursor.fetchall()
|
||||
|
||||
return items
|
||||
self.embycursor.execute(query, (parentid, mediatype,))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getChecksum(self, mediatype):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, checksum",
|
||||
"FROM emby",
|
||||
"WHERE emby_type = ?"
|
||||
))
|
||||
embycursor.execute(query, (mediatype,))
|
||||
items = embycursor.fetchall()
|
||||
self.embycursor.execute(query, (mediatype,))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
return items
|
||||
|
||||
def getMediaType_byId(self, embyid):
|
||||
|
||||
embycursor = self.embycursor
|
||||
def getMediaType_byId(self, plexid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -324,9 +293,10 @@ class Embydb_Functions():
|
|||
"FROM emby",
|
||||
"WHERE emby_id = ?"
|
||||
))
|
||||
embycursor.execute(query, (embyid,))
|
||||
self.embycursor.execute(query, (plexid,))
|
||||
try:
|
||||
itemtype = embycursor.fetchone()[0]
|
||||
itemtype = self.embycursor.fetchone()[0]
|
||||
|
||||
except TypeError:
|
||||
itemtype = None
|
||||
|
||||
|
@ -345,7 +315,7 @@ class Embydb_Functions():
|
|||
|
||||
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):
|
||||
query = (
|
||||
'''
|
||||
|
@ -356,18 +326,18 @@ class Embydb_Functions():
|
|||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype,
|
||||
self.embycursor.execute(query, (plexid, kodiid, fileid, pathid, embytype, mediatype,
|
||||
parentid, checksum, mediafolderid))
|
||||
|
||||
def updateReference(self, embyid, checksum):
|
||||
def updateReference(self, plexid, checksum):
|
||||
|
||||
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 = ?"
|
||||
self.embycursor.execute(query, (parent_kodiid, embyid))
|
||||
self.embycursor.execute(query, (parent_kodiid, plexid))
|
||||
|
||||
def removeItems_byParentId(self, parent_kodiid, mediatype):
|
||||
|
||||
|
@ -389,13 +359,13 @@ class Embydb_Functions():
|
|||
))
|
||||
self.embycursor.execute(query, (kodiid, mediatype,))
|
||||
|
||||
def removeItem(self, embyid):
|
||||
def removeItem(self, plexid):
|
||||
|
||||
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 ?"
|
||||
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 PlexAPI
|
||||
from PlexFunctions import GetMachineIdentifier
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -20,129 +21,271 @@ import PlexAPI
|
|||
class InitialSetup():
|
||||
|
||||
def __init__(self):
|
||||
self.logMsg('Entering initialsetup class', 1)
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonId = self.clientInfo.getAddonId()
|
||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||
self.userClient = userclient.UserClient()
|
||||
self.plx = PlexAPI.PlexAPI()
|
||||
self.dialog = xbmcgui.Dialog()
|
||||
|
||||
def setup(self, forcePlexTV=False, chooseServer=False):
|
||||
"""
|
||||
Initial setup. Run once upon startup.
|
||||
Check server, user, direct paths, music, direct stream if not direct
|
||||
path.
|
||||
"""
|
||||
string = xbmcaddon.Addon().getLocalizedString
|
||||
# SERVER INFO #####
|
||||
self.logMsg("Initial setup called.", 0)
|
||||
server = self.userClient.getServer()
|
||||
serverid = utils.settings('plex_machineIdentifier')
|
||||
self.string = xbmcaddon.Addon().getLocalizedString
|
||||
|
||||
self.server = self.userClient.getServer()
|
||||
self.serverid = utils.settings('plex_machineIdentifier')
|
||||
# Get Plex credentials from settings file, if they exist
|
||||
plexdict = self.plx.GetPlexLoginFromSettings()
|
||||
myplexlogin = plexdict['myplexlogin']
|
||||
plexLogin = plexdict['plexLogin']
|
||||
plexToken = plexdict['plexToken']
|
||||
plexid = plexdict['plexid']
|
||||
if plexToken:
|
||||
self.logMsg('Found plex.tv token in settings', 0)
|
||||
self.myplexlogin = plexdict['myplexlogin'] == 'true'
|
||||
self.plexLogin = plexdict['plexLogin']
|
||||
self.plexToken = plexdict['plexToken']
|
||||
self.plexid = plexdict['plexid']
|
||||
if self.plexToken:
|
||||
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
|
||||
# as plexToken will be ''
|
||||
if (plexToken and myplexlogin == 'true' and forcePlexTV is False
|
||||
and chooseServer is False):
|
||||
chk = self.plx.CheckConnection('plex.tv', plexToken)
|
||||
try:
|
||||
chk.attrib
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
# Success - we downloaded an xml!
|
||||
chk = 200
|
||||
Returns True if successful, or False if not
|
||||
"""
|
||||
result = self.plx.PlexTvSignInWithPin()
|
||||
if result:
|
||||
self.plexLogin = result['username']
|
||||
self.plexToken = result['token']
|
||||
self.plexid = result['plexid']
|
||||
return True
|
||||
return False
|
||||
|
||||
def CheckPlexTVSignIn(self):
|
||||
"""
|
||||
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
|
||||
if chk in (401, 403):
|
||||
self.logMsg('plex.tv connection returned HTTP %s' % chk, 0)
|
||||
# Delete token in the settings
|
||||
utils.settings('plexToken', value='')
|
||||
# Could not login, please try again
|
||||
dialog.ok(self.addonName,
|
||||
string(39009))
|
||||
result = self.plx.PlexTvSignInWithPin()
|
||||
if result:
|
||||
plexLogin = result['username']
|
||||
plexToken = result['token']
|
||||
plexid = result['plexid']
|
||||
elif chk is False or chk >= 400:
|
||||
# Problems connecting to plex.tv. Network or internet issue?
|
||||
self.logMsg('plex.tv connection returned HTTP %s'
|
||||
% str(chk), 0)
|
||||
dialog.ok(self.addonName,
|
||||
string(39010))
|
||||
self.logMsg('plex.tv connection returned HTTP %s' % str(chk), 1)
|
||||
# Delete token in the settings
|
||||
utils.settings('plexToken', value='')
|
||||
utils.settings('plexLogin', value='')
|
||||
# Could not login, please try again
|
||||
self.dialog.ok(self.addonName,
|
||||
self.string(39009))
|
||||
answer = self.PlexTVSignIn()
|
||||
elif chk is False or chk >= 400:
|
||||
# Problems connecting to plex.tv. Network or internet issue?
|
||||
self.logMsg('Problems connecting to plex.tv; connection returned '
|
||||
'HTTP %s' % str(chk), 1)
|
||||
self.dialog.ok(self.addonName,
|
||||
self.string(39010))
|
||||
answer = False
|
||||
else:
|
||||
self.logMsg('plex.tv connection with token successful', 1)
|
||||
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:
|
||||
self.logMsg('plex.tv connection with token successful', 0)
|
||||
# Refresh the info from Plex.tv
|
||||
xml = self.doUtils('https://plex.tv/users/account',
|
||||
authenticate=False,
|
||||
headerOptions={'X-Plex-Token': plexToken})
|
||||
try:
|
||||
xml.attrib
|
||||
except:
|
||||
self.logMsg('Failed to update Plex info from plex.tv', -1)
|
||||
utils.settings('plexLogin', value=self.plexLogin)
|
||||
home = 'true' if xml.attrib.get('home') == '1' else 'false'
|
||||
utils.settings('plexhome', value=home)
|
||||
utils.settings('plexAvatar', value=xml.attrib.get('thumb'))
|
||||
utils.settings(
|
||||
'plexHomeSize', value=xml.attrib.get('homeSize', '1'))
|
||||
self.logMsg('Updated Plex info from plex.tv', 1)
|
||||
return answer
|
||||
|
||||
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:
|
||||
plexLogin = xml.attrib.get('title')
|
||||
utils.settings('plexLogin', value=plexLogin)
|
||||
home = 'true' if xml.attrib.get('home') == '1' else 'false'
|
||||
utils.settings('plexhome', value=home)
|
||||
utils.settings('plexAvatar', value=xml.attrib.get('thumb'))
|
||||
utils.settings(
|
||||
'plexHomeSize', value=xml.attrib.get('homeSize', '1'))
|
||||
self.logMsg('Updated Plex info from plex.tv', 0)
|
||||
return
|
||||
# Problems connecting
|
||||
elif chk >= 400 or chk is False:
|
||||
self.logMsg('Problems connecting to server %s. chk is %s'
|
||||
% (server['name'], chk), -1)
|
||||
return
|
||||
self.logMsg('We found a server to automatically connect to: %s'
|
||||
% server['name'], 1)
|
||||
return server
|
||||
|
||||
# If a Plex server IP has already been set, return.
|
||||
if server and forcePlexTV is False and chooseServer is False:
|
||||
self.logMsg("Server is already set.", 0)
|
||||
self.logMsg("url: %s, Plex machineIdentifier: %s"
|
||||
% (server, serverid), 0)
|
||||
return
|
||||
def _UserPickPMS(self):
|
||||
"""
|
||||
Lets user pick his/her PMS from a list
|
||||
|
||||
# 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 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)
|
||||
Returns server or None if unsuccessful
|
||||
"""
|
||||
httpsUpdated = False
|
||||
while True:
|
||||
if httpsUpdated is False:
|
||||
# Populate g_PMS variable with the found Plex servers
|
||||
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 = []
|
||||
serverlist = self._getServerList()
|
||||
# Exit if no servers found
|
||||
if len(serverlist) == 0:
|
||||
dialog.ok(
|
||||
self.addonName,
|
||||
string(39011)
|
||||
)
|
||||
break
|
||||
self.logMsg('No plex media servers found!', -1)
|
||||
self.dialog.ok(self.addonName, self.string(39011))
|
||||
return
|
||||
# Get a nicer list
|
||||
dialoglist = []
|
||||
for server in serverlist:
|
||||
if server['local'] == '1':
|
||||
# server is in the same network as client. Add "local"
|
||||
msg = string(39022)
|
||||
# server is in the same network as client.
|
||||
# Add"local"
|
||||
msg = self.string(39022)
|
||||
else:
|
||||
# Add 'remote'
|
||||
msg = string(39054)
|
||||
msg = self.string(39054)
|
||||
if server.get('ownername'):
|
||||
# Display username if its not our PMS
|
||||
dialoglist.append('%s (%s, %s)'
|
||||
|
@ -151,33 +294,12 @@ class InitialSetup():
|
|||
msg))
|
||||
else:
|
||||
dialoglist.append('%s (%s)'
|
||||
% (server['name'],
|
||||
msg))
|
||||
resp = dialog.select(string(39012), dialoglist)
|
||||
% (server['name'], msg))
|
||||
# Let user pick server from a list
|
||||
resp = self.dialog.select(self.string(39012), dialoglist)
|
||||
|
||||
server = serverlist[resp]
|
||||
activeServer = server['machineIdentifier']
|
||||
# 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)
|
||||
chk = self._checkServerCon(server)
|
||||
if chk == 504 and httpsUpdated is False:
|
||||
# Not able to use HTTP, try HTTPs for now
|
||||
serverlist[resp]['scheme'] = 'https'
|
||||
|
@ -185,68 +307,124 @@ class InitialSetup():
|
|||
continue
|
||||
httpsUpdated = False
|
||||
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
|
||||
dialog.ok(self.addonName,
|
||||
string(39013) + server['name'],
|
||||
string(39014))
|
||||
result = self.plx.PlexTvSignInWithPin()
|
||||
if result:
|
||||
plexLogin = result['username']
|
||||
plexToken = result['token']
|
||||
plexid = result['plexid']
|
||||
else:
|
||||
self.dialog.ok(self.addonName,
|
||||
self.string(39013) + server['name'],
|
||||
self.string(39014))
|
||||
if self.PlexTVSignIn() is False:
|
||||
# Exit while loop if user cancels
|
||||
break
|
||||
return
|
||||
# Problems connecting
|
||||
elif chk >= 400 or chk is False:
|
||||
# Problems connecting to server. Pick another server?
|
||||
resp = dialog.yesno(self.addonName,
|
||||
string(39015))
|
||||
answ = self.dialog.yesno(self.addonName,
|
||||
self.string(39015))
|
||||
# Exit while loop if user chooses No
|
||||
if not resp:
|
||||
break
|
||||
if not answ:
|
||||
return
|
||||
# Otherwise: connection worked!
|
||||
else:
|
||||
isconnected = True
|
||||
break
|
||||
if not isconnected:
|
||||
# Enter Kodi settings instead
|
||||
xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId)
|
||||
return
|
||||
# Write to Kodi settings file
|
||||
utils.settings('plex_machineIdentifier', activeServer)
|
||||
return server
|
||||
|
||||
def WritePMStoSettings(self, server):
|
||||
"""
|
||||
Saves server to file settings. server is a dict of the form:
|
||||
{
|
||||
'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
|
||||
}
|
||||
"""
|
||||
utils.settings('plex_machineIdentifier', server['machineIdentifier'])
|
||||
utils.settings('plex_servername', server['name'])
|
||||
utils.settings('plex_serverowned',
|
||||
'true' if server['owned'] == '1'
|
||||
else 'false')
|
||||
# Careful to distinguish local from remote PMS
|
||||
if server['local'] == '1':
|
||||
scheme = server['scheme']
|
||||
utils.settings('ipaddress', server['ip'])
|
||||
utils.settings('port', server['port'])
|
||||
self.logMsg("Setting SSL verify to false, because server is "
|
||||
"local", 1)
|
||||
utils.settings('sslverify', 'false')
|
||||
else:
|
||||
baseURL = server['baseURL'].split(':')
|
||||
scheme = baseURL[0]
|
||||
utils.settings('ipaddress', baseURL[1].replace('//', ''))
|
||||
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':
|
||||
utils.settings('https', 'true')
|
||||
else:
|
||||
utils.settings('https', 'false')
|
||||
# And finally do some logging
|
||||
self.logMsg("Writing to Kodi user settings file", 0)
|
||||
self.logMsg("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s "
|
||||
% (activeServer, server['ip'], server['port'],
|
||||
server['scheme']), 0)
|
||||
% (server['machineIdentifier'], server['ip'],
|
||||
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
|
||||
|
||||
goToSettings = False
|
||||
# Additional settings where the user needs to choose
|
||||
# Direct paths (\\NAS\mymovie.mkv) or addon (http)?
|
||||
if dialog.yesno(heading=self.addonName,
|
||||
line1=string(39027),
|
||||
line2=string(39028),
|
||||
if dialog.yesno(self.addonName,
|
||||
string(39027),
|
||||
string(39028),
|
||||
nolabel="Addon (Default)",
|
||||
yeslabel="Native (Direct Paths)"):
|
||||
self.logMsg("User opted to use direct paths.", 1)
|
||||
|
@ -284,15 +462,36 @@ class InitialSetup():
|
|||
else:
|
||||
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:
|
||||
# Open Settings page now? You will need to restart!
|
||||
goToSettings = dialog.yesno(heading=self.addonName,
|
||||
line1=string(39017))
|
||||
if goToSettings:
|
||||
utils.window('emby_serverStatus', value="Stop")
|
||||
utils.window('plex_serverStatus', value="Stop")
|
||||
xbmc.executebuiltin(
|
||||
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||
else:
|
||||
# "Kodi will now restart to apply the changes"
|
||||
dialog.ok(
|
||||
heading=self.addonName,
|
||||
line1=string(33033))
|
||||
xbmc.executebuiltin('RestartApp')
|
||||
# We should always restart to ensure e.g. Kodi settings for Music
|
||||
# are in use!
|
||||
|
|
|
@ -11,7 +11,6 @@ import xbmcgui
|
|||
import xbmcvfs
|
||||
|
||||
import artwork
|
||||
import downloadutils
|
||||
import utils
|
||||
import embydb_functions as embydb
|
||||
import kodidb_functions as kodidb
|
||||
|
@ -34,7 +33,6 @@ class Items(object):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.doUtils = downloadutils.DownloadUtils()
|
||||
self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
||||
# self.directpath = utils.settings('useDirectPaths') == "1"
|
||||
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
|
||||
# playcount. PMS won't tell us the playcount via websockets
|
||||
if item['state'] in ('stopped', 'ended'):
|
||||
markPlayed = 0.90
|
||||
complete = float(item['viewOffset']) / float(item['duration'])
|
||||
complete = complete * 100
|
||||
self.logMsg('Item %s stopped with completion rate %s percent.'
|
||||
'Mark item played at %s percent.'
|
||||
% (item['ratingKey'],
|
||||
str(complete),
|
||||
utils.settings('markPlayed')), 1)
|
||||
if complete >= float(utils.settings('markPlayed')):
|
||||
% (item['ratingKey'], str(complete), markPlayed), 1)
|
||||
if complete >= markPlayed:
|
||||
self.logMsg('Marking as completely watched in Kodi', 1)
|
||||
try:
|
||||
item['viewCount'] += 1
|
||||
|
@ -309,31 +305,17 @@ class Movies(Items):
|
|||
count = 0
|
||||
for boxset in items:
|
||||
|
||||
title = boxset['Name']
|
||||
if pdialog:
|
||||
percentage = int((float(count) / float(total))*100)
|
||||
pdialog.update(percentage, message=title)
|
||||
pdialog.update(percentage, message=boxset['Name'])
|
||||
count += 1
|
||||
self.add_updateBoxset(boxset)
|
||||
|
||||
@utils.CatchExceptions(warnuser=True)
|
||||
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
|
||||
kodicursor = self.kodicursor
|
||||
emby_db = self.emby_db
|
||||
kodi_db = self.kodi_db
|
||||
artwork = self.artwork
|
||||
API = PlexAPI.API(item)
|
||||
|
||||
|
@ -417,7 +399,7 @@ class Movies(Items):
|
|||
doIndirect = not self.directpath
|
||||
if self.directpath:
|
||||
# Direct paths is set the Kodi way
|
||||
playurl = API.getFilePath()
|
||||
playurl = API.getFilePath(forceFirstMediaStream=True)
|
||||
if playurl is None:
|
||||
# Something went wrong, trying to use non-direct paths
|
||||
doIndirect = True
|
||||
|
@ -436,7 +418,7 @@ class Movies(Items):
|
|||
# Set plugin path and media flags using real filename
|
||||
path = "plugin://plugin.video.plexkodiconnect.movies/"
|
||||
params = {
|
||||
'filename': API.getKey().encode('utf-8'),
|
||||
'filename': API.getKey(),
|
||||
'id': itemid,
|
||||
'dbid': movieid,
|
||||
'mode': "play"
|
||||
|
@ -444,11 +426,6 @@ class Movies(Items):
|
|||
filename = "%s?%s" % (path, urllib.urlencode(params))
|
||||
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:
|
||||
# c22 - playurl
|
||||
# c23 - pathid
|
||||
|
@ -477,6 +454,10 @@ class Movies(Items):
|
|||
##### OR ADD THE MOVIE #####
|
||||
else:
|
||||
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
|
||||
query = (
|
||||
|
@ -514,28 +495,30 @@ class Movies(Items):
|
|||
kodicursor.execute(query, (pathid, filename, dateadded, fileid))
|
||||
|
||||
# Process countries
|
||||
kodi_db.addCountries(movieid, countries, "movie")
|
||||
self.kodi_db.addCountries(movieid, countries, "movie")
|
||||
# Process cast
|
||||
people = API.getPeopleList()
|
||||
kodi_db.addPeople(movieid, people, "movie")
|
||||
self.kodi_db.addPeople(movieid, people, "movie")
|
||||
# Process genres
|
||||
kodi_db.addGenres(movieid, genres, "movie")
|
||||
self.kodi_db.addGenres(movieid, genres, "movie")
|
||||
# Process artwork
|
||||
allartworks = API.getAllArtwork()
|
||||
artwork.addArtwork(allartworks, movieid, "movie", kodicursor)
|
||||
# Process stream details
|
||||
streams = API.getMediaStreams()
|
||||
kodi_db.addStreams(fileid, streams, runtime)
|
||||
self.kodi_db.addStreams(fileid, streams, runtime)
|
||||
# Process studios
|
||||
kodi_db.addStudios(movieid, studios, "movie")
|
||||
self.kodi_db.addStudios(movieid, studios, "movie")
|
||||
# Process tags: view, Plex collection tags
|
||||
tags = [viewtag]
|
||||
tags.extend(collections)
|
||||
if userdata['Favorite']:
|
||||
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
|
||||
kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
||||
self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
||||
|
||||
def remove(self, itemid):
|
||||
# Remove movieid, fileid, emby reference
|
||||
|
@ -566,11 +549,11 @@ class Movies(Items):
|
|||
# Delete kodi boxset
|
||||
boxset_movies = emby_db.getItem_byParentId(kodiid, "movie")
|
||||
for movie in boxset_movies:
|
||||
embyid = movie[0]
|
||||
plexid = movie[0]
|
||||
movieid = movie[1]
|
||||
self.kodi_db.removefromBoxset(movieid)
|
||||
# Update emby reference
|
||||
emby_db.updateParentId(embyid, None)
|
||||
emby_db.updateParentId(plexid, None)
|
||||
|
||||
kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodiid,))
|
||||
|
||||
|
@ -603,9 +586,8 @@ class MusicVideos(Items):
|
|||
# Process single music video
|
||||
kodicursor = self.kodicursor
|
||||
emby_db = self.emby_db
|
||||
kodi_db = self.kodi_db
|
||||
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 doesn't exist, we'll add it to the database
|
||||
|
@ -638,7 +620,7 @@ class MusicVideos(Items):
|
|||
|
||||
if not viewtag or not viewid:
|
||||
# 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)
|
||||
|
||||
# fileId information
|
||||
|
@ -675,7 +657,7 @@ class MusicVideos(Items):
|
|||
|
||||
if self.directpath:
|
||||
# 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
|
||||
resp = xbmcgui.Dialog().yesno(
|
||||
heading="Can't validate path",
|
||||
|
@ -686,17 +668,17 @@ class MusicVideos(Items):
|
|||
"to format your path correctly. Stop syncing?"
|
||||
% playurl))
|
||||
if resp:
|
||||
utils.window('emby_shouldStop', value="true")
|
||||
utils.window('plex_shouldStop', value="true")
|
||||
return False
|
||||
|
||||
path = playurl.replace(filename, "")
|
||||
utils.window('emby_pathverified', value="true")
|
||||
utils.window('plex_pathverified', value="true")
|
||||
else:
|
||||
# Set plugin path and media flags using real filename
|
||||
path = "plugin://plugin.video.plexkodiconnect.musicvideos/"
|
||||
params = {
|
||||
|
||||
'filename': filename.encode('utf-8'),
|
||||
'filename': utils.tryEncode(filename),
|
||||
'id': itemid,
|
||||
'dbid': mvideoid,
|
||||
'mode': "play"
|
||||
|
@ -794,32 +776,31 @@ class MusicVideos(Items):
|
|||
artist['Type'] = "Artist"
|
||||
people.extend(artists)
|
||||
people = artwork.getPeopleArtwork(people)
|
||||
kodi_db.addPeople(mvideoid, people, "musicvideo")
|
||||
self.kodi_db.addPeople(mvideoid, people, "musicvideo")
|
||||
# Process genres
|
||||
kodi_db.addGenres(mvideoid, genres, "musicvideo")
|
||||
self.kodi_db.addGenres(mvideoid, genres, "musicvideo")
|
||||
# Process artwork
|
||||
artwork.addArtwork(artwork.getAllArtwork(item), mvideoid, "musicvideo", kodicursor)
|
||||
# Process stream details
|
||||
streams = API.getMediaStreams()
|
||||
kodi_db.addStreams(fileid, streams, runtime)
|
||||
self.kodi_db.addStreams(fileid, streams, runtime)
|
||||
# Process studios
|
||||
kodi_db.addStudios(mvideoid, studios, "musicvideo")
|
||||
self.kodi_db.addStudios(mvideoid, studios, "musicvideo")
|
||||
# Process tags: view, emby tags
|
||||
tags = [viewtag]
|
||||
tags.extend(item['Tags'])
|
||||
if userdata['Favorite']:
|
||||
tags.append("Favorite musicvideos")
|
||||
kodi_db.addTags(mvideoid, tags, "musicvideo")
|
||||
self.kodi_db.addTags(mvideoid, tags, "musicvideo")
|
||||
# Process playstates
|
||||
resume = API.adjustResume(userdata['Resume'])
|
||||
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):
|
||||
# This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
|
||||
# Poster with progress bar
|
||||
emby_db = self.emby_db
|
||||
kodi_db = self.kodi_db
|
||||
API = api.API(item)
|
||||
|
||||
# Get emby information
|
||||
|
@ -841,9 +822,9 @@ class MusicVideos(Items):
|
|||
|
||||
# Process favorite tags
|
||||
if userdata['Favorite']:
|
||||
kodi_db.addTag(mvideoid, "Favorite musicvideos", "musicvideo")
|
||||
self.kodi_db.addTag(mvideoid, "Favorite musicvideos", "musicvideo")
|
||||
else:
|
||||
kodi_db.removeTag(mvideoid, "Favorite musicvideos", "musicvideo")
|
||||
self.kodi_db.removeTag(mvideoid, "Favorite musicvideos", "musicvideo")
|
||||
|
||||
# Process playstates
|
||||
playcount = userdata['PlayCount']
|
||||
|
@ -851,7 +832,7 @@ class MusicVideos(Items):
|
|||
resume = API.adjustResume(userdata['Resume'])
|
||||
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)
|
||||
|
||||
def remove(self, itemid):
|
||||
|
@ -878,8 +859,7 @@ class MusicVideos(Items):
|
|||
"AND media_type = 'musicvideo'"
|
||||
))
|
||||
kodicursor.execute(query, (mvideoid,))
|
||||
rows = kodicursor.fetchall()
|
||||
for row in rows:
|
||||
for row in kodicursor.fetchall():
|
||||
|
||||
url = row[0]
|
||||
imagetype = row[1]
|
||||
|
@ -942,24 +922,11 @@ class TVShows(Items):
|
|||
if not pdialog and self.contentmsg:
|
||||
self.contentPop(title, self.newvideo_time)
|
||||
|
||||
@utils.CatchExceptions(warnuser=True)
|
||||
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
|
||||
kodicursor = self.kodicursor
|
||||
emby_db = self.emby_db
|
||||
kodi_db = self.kodi_db
|
||||
artwork = self.artwork
|
||||
API = PlexAPI.API(item)
|
||||
|
||||
|
@ -997,7 +964,7 @@ class TVShows(Items):
|
|||
|
||||
if viewtag is None or viewid is None:
|
||||
# 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)
|
||||
|
||||
# fileId information
|
||||
|
@ -1047,7 +1014,7 @@ class TVShows(Items):
|
|||
path = "%s%s/" % (toplevelpath, itemid)
|
||||
|
||||
# Add top path
|
||||
toppathid = kodi_db.addPath(toplevelpath)
|
||||
toppathid = self.kodi_db.addPath(toplevelpath)
|
||||
# UPDATE THE TVSHOW #####
|
||||
if update_item:
|
||||
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))
|
||||
|
||||
# Add path
|
||||
pathid = kodi_db.addPath(path)
|
||||
pathid = self.kodi_db.addPath(path)
|
||||
|
||||
# Create the tvshow entry
|
||||
query = (
|
||||
|
@ -1112,18 +1079,18 @@ class TVShows(Items):
|
|||
|
||||
# Process cast
|
||||
people = API.getPeopleList()
|
||||
kodi_db.addPeople(showid, people, "tvshow")
|
||||
self.kodi_db.addPeople(showid, people, "tvshow")
|
||||
# Process genres
|
||||
kodi_db.addGenres(showid, genres, "tvshow")
|
||||
self.kodi_db.addGenres(showid, genres, "tvshow")
|
||||
# Process artwork
|
||||
allartworks = API.getAllArtwork()
|
||||
artwork.addArtwork(allartworks, showid, "tvshow", kodicursor)
|
||||
# Process studios
|
||||
kodi_db.addStudios(showid, studios, "tvshow")
|
||||
self.kodi_db.addStudios(showid, studios, "tvshow")
|
||||
# Process tags: view, PMS collection tags
|
||||
tags = [viewtag]
|
||||
tags.extend(collections)
|
||||
kodi_db.addTags(showid, tags, "tvshow")
|
||||
self.kodi_db.addTags(showid, tags, "tvshow")
|
||||
|
||||
if force_episodes:
|
||||
# We needed to recreate the show entry. Re-add episodes now.
|
||||
|
@ -1133,20 +1100,8 @@ class TVShows(Items):
|
|||
self.kodiconn.commit()
|
||||
self.embyconn.commit()
|
||||
|
||||
@utils.CatchExceptions(warnuser=True)
|
||||
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)
|
||||
itemid = API.getRatingKey()
|
||||
if not itemid:
|
||||
|
@ -1154,7 +1109,6 @@ class TVShows(Items):
|
|||
return
|
||||
kodicursor = self.kodicursor
|
||||
emby_db = self.emby_db
|
||||
kodi_db = self.kodi_db
|
||||
artwork = self.artwork
|
||||
seasonnum = API.getIndex()
|
||||
# Get parent tv show Plex id
|
||||
|
@ -1169,7 +1123,7 @@ class TVShows(Items):
|
|||
% (itemid), -1)
|
||||
return
|
||||
|
||||
seasonid = kodi_db.addSeason(showid, seasonnum)
|
||||
seasonid = self.kodi_db.addSeason(showid, seasonnum)
|
||||
checksum = API.getChecksum()
|
||||
# Check whether Season already exists
|
||||
update_item = True
|
||||
|
@ -1192,28 +1146,14 @@ class TVShows(Items):
|
|||
self.kodiconn.commit()
|
||||
self.embyconn.commit()
|
||||
|
||||
@utils.CatchExceptions(warnuser=True)
|
||||
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!
|
||||
"""
|
||||
# Process single episode
|
||||
kodiversion = self.kodiversion
|
||||
kodicursor = self.kodicursor
|
||||
emby_db = self.emby_db
|
||||
kodi_db = self.kodi_db
|
||||
artwork = self.artwork
|
||||
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("Parent tvshow now found, skip item", 2)
|
||||
return False
|
||||
seasonid = kodi_db.addSeason(showid, season)
|
||||
seasonid = self.kodi_db.addSeason(showid, season)
|
||||
|
||||
# GET THE FILE AND PATH #####
|
||||
doIndirect = not self.directpath
|
||||
playurl = API.getFilePath()
|
||||
playurl = API.getFilePath(forceFirstMediaStream=True)
|
||||
if self.directpath:
|
||||
# Direct paths is set the Kodi way
|
||||
if playurl is None:
|
||||
|
@ -1331,7 +1271,7 @@ class TVShows(Items):
|
|||
# Network share
|
||||
filename = playurl.rsplit("/", 1)[1]
|
||||
path = playurl.replace(filename, "")
|
||||
parentPathId = kodi_db.getParentPathId(path)
|
||||
parentPathId = self.kodi_db.getParentPathId(path)
|
||||
if doIndirect:
|
||||
# Set plugin path and media flags using real filename
|
||||
if playurl is not None:
|
||||
|
@ -1340,24 +1280,20 @@ class TVShows(Items):
|
|||
else:
|
||||
filename = playurl.rsplit('/', 1)[1]
|
||||
else:
|
||||
filename = 'file_not_found'
|
||||
filename = 'file_not_found.mkv'
|
||||
path = "plugin://plugin.video.plexkodiconnect.tvshows/%s/" % seriesId
|
||||
params = {
|
||||
'filename': filename.encode('utf-8'),
|
||||
'filename': utils.tryEncode(filename),
|
||||
'id': itemid,
|
||||
'dbid': episodeid,
|
||||
'mode': "play"
|
||||
}
|
||||
filename = "%s?%s" % (path, urllib.urlencode(params))
|
||||
filename = "%s?%s" % (path,
|
||||
utils.tryDecode(urllib.urlencode(params)))
|
||||
playurl = filename
|
||||
parentPathId = kodi_db.addPath(
|
||||
parentPathId = self.kodi_db.addPath(
|
||||
'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:
|
||||
# c18 - playurl
|
||||
# c19 - pathid
|
||||
|
@ -1368,7 +1304,7 @@ class TVShows(Items):
|
|||
self.logMsg("UPDATE episode itemid: %s" % (itemid), 1)
|
||||
|
||||
# Update the movie entry
|
||||
if kodiversion in (16, 17):
|
||||
if self.kodiversion in (16, 17):
|
||||
# Kodi Jarvis, Krypton
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -1400,10 +1336,13 @@ class TVShows(Items):
|
|||
|
||||
##### OR ADD THE EPISODE #####
|
||||
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
|
||||
if kodiversion in (16, 17):
|
||||
if self.kodiversion in (16, 17):
|
||||
# Kodi Jarvis, Krypton
|
||||
query = (
|
||||
'''
|
||||
|
@ -1454,7 +1393,7 @@ class TVShows(Items):
|
|||
kodicursor.execute(query, (pathid, filename, dateadded, fileid))
|
||||
# Process cast
|
||||
people = API.getPeopleList()
|
||||
kodi_db.addPeople(episodeid, people, "episode")
|
||||
self.kodi_db.addPeople(episodeid, people, "episode")
|
||||
# Process artwork
|
||||
# Wide "screenshot" of particular episode
|
||||
poster = item.attrib.get('thumb')
|
||||
|
@ -1473,13 +1412,13 @@ class TVShows(Items):
|
|||
|
||||
# Process stream details
|
||||
streams = API.getMediaStreams()
|
||||
kodi_db.addStreams(fileid, streams, runtime)
|
||||
self.kodi_db.addStreams(fileid, streams, runtime)
|
||||
# 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:
|
||||
# Create additional entry for widgets. This is only required for plugin/episode.
|
||||
temppathid = kodi_db.getPath("plugin://plugin.video.plexkodiconnect.tvshows/")
|
||||
tempfileid = kodi_db.addFile(filename, temppathid)
|
||||
temppathid = self.kodi_db.getPath("plugin://plugin.video.plexkodiconnect.tvshows/")
|
||||
tempfileid = self.kodi_db.addFile(filename, temppathid)
|
||||
query = ' '.join((
|
||||
|
||||
"UPDATE files",
|
||||
|
@ -1487,7 +1426,7 @@ class TVShows(Items):
|
|||
"WHERE idFile = ?"
|
||||
))
|
||||
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.embyconn.commit()
|
||||
|
||||
|
@ -1600,27 +1539,23 @@ class TVShows(Items):
|
|||
def removeShow(self, kodiid):
|
||||
|
||||
kodicursor = self.kodicursor
|
||||
artwork = self.artwork
|
||||
|
||||
artwork.deleteArtwork(kodiid, "tvshow", kodicursor)
|
||||
self.artwork.deleteArtwork(kodiid, "tvshow", kodicursor)
|
||||
kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodiid,))
|
||||
self.logMsg("Removed tvshow: %s." % kodiid, 2)
|
||||
|
||||
def removeSeason(self, kodiid):
|
||||
|
||||
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,))
|
||||
self.logMsg("Removed season: %s." % kodiid, 2)
|
||||
|
||||
def removeEpisode(self, kodiid, fileid):
|
||||
|
||||
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 files WHERE idFile = ?", (fileid,))
|
||||
self.logMsg("Removed episode: %s." % kodiid, 2)
|
||||
|
@ -1656,10 +1591,9 @@ class Music(Items):
|
|||
count = 0
|
||||
for artist in items:
|
||||
|
||||
title = artist['Name']
|
||||
if pdialog:
|
||||
percentage = int((float(count) / float(total))*100)
|
||||
pdialog.update(percentage, message=title)
|
||||
pdialog.update(percentage, message=artist['Name'])
|
||||
count += 1
|
||||
self.add_updateArtist(artist)
|
||||
# Add albums
|
||||
|
@ -1672,10 +1606,9 @@ class Music(Items):
|
|||
count = 0
|
||||
for album in items:
|
||||
|
||||
title = album['Name']
|
||||
if pdialog:
|
||||
percentage = int((float(count) / float(total))*100)
|
||||
pdialog.update(percentage, message=title)
|
||||
pdialog.update(percentage, message=album['Name'])
|
||||
count += 1
|
||||
self.add_updateAlbum(album)
|
||||
# Add songs
|
||||
|
@ -1688,34 +1621,19 @@ class Music(Items):
|
|||
count = 0
|
||||
for song in items:
|
||||
|
||||
title = song['Name']
|
||||
if pdialog:
|
||||
percentage = int((float(count) / float(total))*100)
|
||||
pdialog.update(percentage, message=title)
|
||||
pdialog.update(percentage, message=song['Name'])
|
||||
count += 1
|
||||
self.add_updateSong(song)
|
||||
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"):
|
||||
try:
|
||||
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,
|
||||
@utils.CatchExceptions(warnuser=True)
|
||||
def add_updateArtist(self, item, viewtag=None, viewid=None,
|
||||
artisttype="MusicArtist"):
|
||||
kodicursor = self.kodicursor
|
||||
emby_db = self.emby_db
|
||||
kodi_db = self.kodi_db
|
||||
artwork = self.artwork
|
||||
API = PlexAPI.API(item)
|
||||
|
||||
|
@ -1764,7 +1682,7 @@ class Music(Items):
|
|||
# multiple times.
|
||||
# Kodi doesn't allow that. In case that happens we just merge the
|
||||
# artist entries.
|
||||
artistid = kodi_db.addArtist(name, musicBrainzId)
|
||||
artistid = self.kodi_db.addArtist(name, musicBrainzId)
|
||||
# Create the reference in emby table
|
||||
emby_db.addReference(
|
||||
itemid, artistid, artisttype, "artist", checksum=checksum)
|
||||
|
@ -1796,25 +1714,10 @@ class Music(Items):
|
|||
self.embyconn.commit()
|
||||
self.kodiconn.commit()
|
||||
|
||||
@utils.CatchExceptions(warnuser=True)
|
||||
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
|
||||
emby_db = self.emby_db
|
||||
kodi_db = self.kodi_db
|
||||
artwork = self.artwork
|
||||
API = PlexAPI.API(item)
|
||||
|
||||
|
@ -1875,13 +1778,13 @@ class Music(Items):
|
|||
# multiple times.
|
||||
# Kodi doesn't allow that. In case that happens we just merge the
|
||||
# artist entries.
|
||||
albumid = kodi_db.addAlbum(name, musicBrainzId)
|
||||
albumid = self.kodi_db.addAlbum(name, musicBrainzId)
|
||||
# Create the reference in emby table
|
||||
emby_db.addReference(
|
||||
itemid, albumid, "MusicAlbum", "album", checksum=checksum)
|
||||
|
||||
# Process the album info
|
||||
if kodiversion == 17:
|
||||
if self.kodiversion == 17:
|
||||
# Kodi Krypton
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -1894,7 +1797,7 @@ class Music(Items):
|
|||
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
|
||||
rating, lastScraped, "album", studio,
|
||||
albumid))
|
||||
elif kodiversion == 16:
|
||||
elif self.kodiversion == 16:
|
||||
# Kodi Jarvis
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -1907,7 +1810,7 @@ class Music(Items):
|
|||
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
|
||||
rating, lastScraped, "album", studio,
|
||||
albumid))
|
||||
elif kodiversion == 15:
|
||||
elif self.kodiversion == 15:
|
||||
# Kodi Isengard
|
||||
query = ' '.join((
|
||||
|
||||
|
@ -1998,33 +1901,18 @@ class Music(Items):
|
|||
# Update emby reference with parentid
|
||||
emby_db.updateParentId(artistId, albumid)
|
||||
# Add genres
|
||||
kodi_db.addMusicGenres(albumid, genres, "album")
|
||||
self.kodi_db.addMusicGenres(albumid, genres, "album")
|
||||
# Update artwork
|
||||
artwork.addArtwork(artworks, albumid, "album", kodicursor)
|
||||
self.embyconn.commit()
|
||||
self.kodiconn.commit()
|
||||
|
||||
@utils.CatchExceptions(warnuser=True)
|
||||
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
|
||||
kodiversion = self.kodiversion
|
||||
kodicursor = self.kodicursor
|
||||
emby = self.emby
|
||||
emby_db = self.emby_db
|
||||
kodi_db = self.kodi_db
|
||||
artwork = self.artwork
|
||||
API = PlexAPI.API(item)
|
||||
|
||||
|
@ -2085,7 +1973,7 @@ class Music(Items):
|
|||
doIndirect = not self.directpath
|
||||
if self.directpath:
|
||||
# Direct paths is set the Kodi way
|
||||
playurl = API.getFilePath()
|
||||
playurl = API.getFilePath(forceFirstMediaStream=True)
|
||||
if playurl is None:
|
||||
# Something went wrong, trying to use non-direct paths
|
||||
doIndirect = True
|
||||
|
@ -2136,7 +2024,7 @@ class Music(Items):
|
|||
self.logMsg("ADD song itemid: %s - Title: %s" % (itemid, title), 1)
|
||||
|
||||
# Add path
|
||||
pathid = kodi_db.addPath(path, strHash="123")
|
||||
pathid = self.kodi_db.addPath(path, strHash="123")
|
||||
|
||||
try:
|
||||
# Get the album
|
||||
|
@ -2148,7 +2036,7 @@ class Music(Items):
|
|||
album_name = item.get('parentTitle')
|
||||
if album_name:
|
||||
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")
|
||||
else:
|
||||
# No album Id associated to the song.
|
||||
|
@ -2173,7 +2061,7 @@ class Music(Items):
|
|||
self.logMsg("Failed to add album. Creating singles.", 1)
|
||||
kodicursor.execute("select coalesce(max(idAlbum),0) from album")
|
||||
albumid = kodicursor.fetchone()[0] + 1
|
||||
if kodiversion == 16:
|
||||
if self.kodiversion == 16:
|
||||
# Kodi Jarvis
|
||||
query = (
|
||||
'''
|
||||
|
@ -2183,7 +2071,7 @@ class Music(Items):
|
|||
'''
|
||||
)
|
||||
kodicursor.execute(query, (albumid, genre, year, "single"))
|
||||
elif kodiversion == 15:
|
||||
elif self.kodiversion == 15:
|
||||
# Kodi Isengard
|
||||
query = (
|
||||
'''
|
||||
|
@ -2262,13 +2150,31 @@ class Music(Items):
|
|||
artist_edb = emby_db.getItem_byId(artist_eid)
|
||||
artistid = artist_edb[0]
|
||||
finally:
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO song_artist(idArtist, idSong, iOrder, strArtist)
|
||||
VALUES (?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
kodicursor.execute(query, (artistid, songid, index, artist_name))
|
||||
if self.kodiversion >= 17:
|
||||
# Kodi Krypton
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO song_artist(idArtist, idSong, idRole, iOrder, strArtist)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
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
|
||||
album_artists = []
|
||||
|
@ -2316,11 +2222,11 @@ class Music(Items):
|
|||
result = kodicursor.fetchone()
|
||||
if result and result[0] != album_artists:
|
||||
# Field is empty
|
||||
if kodiversion in (16, 17):
|
||||
if self.kodiversion in (16, 17):
|
||||
# Kodi Jarvis, Krypton
|
||||
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
|
||||
kodicursor.execute(query, (album_artists, albumid))
|
||||
elif kodiversion == 15:
|
||||
elif self.kodiversion == 15:
|
||||
# Kodi Isengard
|
||||
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
|
||||
kodicursor.execute(query, (album_artists, albumid))
|
||||
|
@ -2330,7 +2236,7 @@ class Music(Items):
|
|||
kodicursor.execute(query, (album_artists, albumid))
|
||||
|
||||
# Add genres
|
||||
kodi_db.addMusicGenres(songid, genres, "song")
|
||||
self.kodi_db.addMusicGenres(songid, genres, "song")
|
||||
|
||||
# Update artwork
|
||||
allart = API.getAllArtwork(parentInfo=True)
|
||||
|
@ -2372,10 +2278,9 @@ class Music(Items):
|
|||
self.removeSong(kodiid)
|
||||
# This should only address single song scenario, where server doesn't actually
|
||||
# create an album for the song.
|
||||
customitems = emby_db.getItem_byWildId(itemid)
|
||||
emby_db.removeWildItem(itemid)
|
||||
|
||||
for item in customitems:
|
||||
for item in emby_db.getItem_byWildId(itemid):
|
||||
|
||||
item_kid = item[0]
|
||||
item_mediatype = item[1]
|
||||
|
@ -2431,23 +2336,16 @@ class Music(Items):
|
|||
def removeSong(self, kodiid):
|
||||
|
||||
kodicursor = self.kodicursor
|
||||
artwork = self.artwork
|
||||
|
||||
artwork.deleteArtwork(kodiid, "song", kodicursor)
|
||||
kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiid,))
|
||||
self.artwork.deleteArtwork(kodiid, "song", self.kodicursor)
|
||||
self.kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiid,))
|
||||
|
||||
def removeAlbum(self, kodiid):
|
||||
|
||||
kodicursor = self.kodicursor
|
||||
artwork = self.artwork
|
||||
|
||||
artwork.deleteArtwork(kodiid, "album", kodicursor)
|
||||
kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiid,))
|
||||
self.artwork.deleteArtwork(kodiid, "album", self.kodicursor)
|
||||
self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiid,))
|
||||
|
||||
def removeArtist(self, kodiid):
|
||||
|
||||
kodicursor = self.kodicursor
|
||||
artwork = self.artwork
|
||||
|
||||
artwork.deleteArtwork(kodiid, "artist", kodicursor)
|
||||
kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiid,))
|
||||
self.artwork.deleteArtwork(kodiid, "artist", self.kodicursor)
|
||||
self.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.xbmcplayer = xbmc.Player()
|
||||
|
||||
xbmc.Monitor.__init__(self)
|
||||
self.logMsg("Kodi monitor started.", 1)
|
||||
|
||||
def onScanStarted(self, library):
|
||||
self.logMsg("Kodi library scan %s running." % library, 2)
|
||||
if library == "video":
|
||||
utils.window('emby_kodiScan', value="true")
|
||||
utils.window('plex_kodiScan', value="true")
|
||||
|
||||
def onScanFinished(self, library):
|
||||
self.logMsg("Kodi library scan %s finished." % library, 2)
|
||||
if library == "video":
|
||||
utils.window('emby_kodiScan', clear=True)
|
||||
utils.window('plex_kodiScan', clear=True)
|
||||
|
||||
def onSettingsChanged(self):
|
||||
# Monitor emby settings
|
||||
# Review reset setting at a later time, need to be adjusted to account for initial setup
|
||||
# changes.
|
||||
'''currentPath = utils.settings('useDirectPaths')
|
||||
if utils.window('emby_pluginpath') != currentPath:
|
||||
if utils.window('plex_pluginpath') != currentPath:
|
||||
# Plugin path value changed. Offer to reset
|
||||
self.logMsg("Changed to playback mode detected", 1)
|
||||
utils.window('emby_pluginpath', value=currentPath)
|
||||
utils.window('plex_pluginpath', value=currentPath)
|
||||
resp = xbmcgui.Dialog().yesno(
|
||||
heading="Playback mode change detected",
|
||||
line1=(
|
||||
|
@ -56,17 +56,17 @@ class KodiMonitor(xbmc.Monitor):
|
|||
utils.reset()'''
|
||||
|
||||
currentLog = utils.settings('logLevel')
|
||||
if utils.window('emby_logLevel') != currentLog:
|
||||
if utils.window('plex_logLevel') != currentLog:
|
||||
# The log level changed, set new prop
|
||||
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):
|
||||
if method not in ("Playlist.OnAdd"):
|
||||
self.logMsg("Method: %s Data: %s" % (method, data), 1)
|
||||
|
||||
if data:
|
||||
data = json.loads(data, 'utf-8')
|
||||
self.logMsg("Method: %s Data: %s" % (method, data), 1)
|
||||
|
||||
if method == "Player.OnPlay":
|
||||
self.PlayBackStart(data)
|
||||
|
@ -82,13 +82,13 @@ class KodiMonitor(xbmc.Monitor):
|
|||
item = data.get('item')
|
||||
try:
|
||||
kodiid = item['id']
|
||||
type = item['type']
|
||||
item_type = item['type']
|
||||
except (KeyError, TypeError):
|
||||
self.logMsg("Item is invalid for playstate update.", 1)
|
||||
else:
|
||||
# Send notification to the server.
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
|
||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type)
|
||||
try:
|
||||
itemid = emby_dbitem[0]
|
||||
except TypeError:
|
||||
|
@ -137,14 +137,19 @@ class KodiMonitor(xbmc.Monitor):
|
|||
|
||||
url = "{server}/emby/Items/%s?format=json" % itemid
|
||||
self.logMsg("Deleting request: %s" % itemid)
|
||||
doUtils.downloadUrl(url, type="DELETE")
|
||||
doUtils.downloadUrl(url, action_type="DELETE")
|
||||
finally:
|
||||
embycursor.close()'''
|
||||
|
||||
elif method == "System.OnWake":
|
||||
# Allow network to wake up
|
||||
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":
|
||||
pass
|
||||
|
@ -155,7 +160,6 @@ class KodiMonitor(xbmc.Monitor):
|
|||
"""
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
|
||||
# Get currently playing file - can take a while. Will be utf-8!
|
||||
try:
|
||||
currentFile = self.xbmcplayer.getPlayingFile()
|
||||
|
@ -173,53 +177,77 @@ class KodiMonitor(xbmc.Monitor):
|
|||
return
|
||||
else:
|
||||
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
|
||||
item = data.get('item')
|
||||
# Get the type of media we're playing
|
||||
try:
|
||||
type = item['type']
|
||||
except:
|
||||
typus = data['item']['type']
|
||||
except (TypeError, KeyError):
|
||||
log("Item is invalid for PMS playstate update.", 0)
|
||||
return
|
||||
try:
|
||||
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
|
||||
log("Playing itemtype is (or appears to be): %s" % typus, 1)
|
||||
|
||||
# Get Plex' item id
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
|
||||
try:
|
||||
plexid = emby_dbitem[0]
|
||||
except TypeError:
|
||||
log("No Plex id returned for kodiid %s" % kodiid, 0)
|
||||
return
|
||||
log("Found Plex id %s for Kodi id %s" % (plexid, kodiid), 1)
|
||||
# Try to get a Kodi ID
|
||||
# If PKC was used - native paths, not direct paths
|
||||
plexid = utils.window('emby_%s.itemid'
|
||||
% utils.tryEncode(currentFile))
|
||||
# Get rid of the '' if the window property was not set
|
||||
plexid = None if not plexid else plexid
|
||||
kodiid = None
|
||||
if plexid is None:
|
||||
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
|
||||
if ((utils.settings('useDirectPaths') == "1" and not type == "song") or
|
||||
(type == "song" and utils.settings('enableMusic') == "true")):
|
||||
if self.StartDirectPath(plexid, type, currentFile) is False:
|
||||
if ((utils.settings('useDirectPaths') == "1" and not typus == "song")
|
||||
or
|
||||
(typus == "song" and utils.settings('enableMusic') == "true")):
|
||||
if self.StartDirectPath(plexid,
|
||||
typus,
|
||||
utils.tryEncode(currentFile)) is False:
|
||||
log('Could not initiate monitoring; aborting', -1)
|
||||
return
|
||||
|
||||
# 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("emby_%s.itemid" % currentFile, value=plexid)
|
||||
window("emby_%s.itemid" % utils.tryEncode(currentFile), value=plexid)
|
||||
log('Finish playback startup', 1)
|
||||
|
||||
def StartDirectPath(self, plexid, type, currentFile):
|
||||
|
|
|
@ -255,7 +255,7 @@ class ThreadedShowSyncInfo(Thread):
|
|||
|
||||
@utils.logging
|
||||
@utils.ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
|
||||
@utils.ThreadMethodsAdditionalStop('emby_shouldStop')
|
||||
@utils.ThreadMethodsAdditionalStop('plex_shouldStop')
|
||||
@utils.ThreadMethods
|
||||
class LibrarySync(Thread):
|
||||
"""
|
||||
|
@ -287,7 +287,6 @@ class LibrarySync(Thread):
|
|||
self.vnodes = videonodes.VideoNodes()
|
||||
self.dialog = xbmcgui.Dialog()
|
||||
|
||||
self.syncThreadNumber = int(utils.settings('syncThreadNumber'))
|
||||
self.installSyncDone = True if \
|
||||
utils.settings('SyncInstallRunDone') == 'true' else False
|
||||
self.showDbSync = True if \
|
||||
|
@ -298,8 +297,8 @@ class LibrarySync(Thread):
|
|||
'enableBackgroundSync') == "true" else False
|
||||
self.limitindex = int(utils.settings('limitindex'))
|
||||
|
||||
if utils.settings('emby_pathverified') == 'true':
|
||||
utils.window('emby_pathverified', value='true')
|
||||
if utils.settings('plex_pathverified') == 'true':
|
||||
utils.window('plex_pathverified', value='true')
|
||||
|
||||
# Just in case a time sync goes wrong
|
||||
self.timeoffset = int(utils.settings('kodiplextimeoffset'))
|
||||
|
@ -477,9 +476,6 @@ class LibrarySync(Thread):
|
|||
# Add sources
|
||||
utils.sourcesXML()
|
||||
|
||||
# Ensure that DBs exist if called for very first time
|
||||
self.initializeDBs()
|
||||
|
||||
# Set views. Abort if unsuccessful
|
||||
if not self.maintainViews():
|
||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
||||
|
@ -507,7 +503,7 @@ class LibrarySync(Thread):
|
|||
if self.enableMusic:
|
||||
xbmc.executebuiltin('UpdateLibrary(music)')
|
||||
|
||||
utils.window('emby_initialScan', clear=True)
|
||||
utils.window('plex_initialScan', clear=True)
|
||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
||||
utils.setScreensaver(value=screensaver)
|
||||
if utils.window('plex_scancrashed') == 'true':
|
||||
|
@ -516,7 +512,7 @@ class LibrarySync(Thread):
|
|||
utils.window('plex_scancrashed', clear=True)
|
||||
elif utils.window('plex_scancrashed') == '401':
|
||||
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
|
||||
self.dialog.ok(self.addonName, self.__language__(39409))
|
||||
|
||||
|
@ -536,7 +532,7 @@ class LibrarySync(Thread):
|
|||
folder = folderItem.attrib
|
||||
mediatype = folder['type']
|
||||
# Only process supported formats
|
||||
if mediatype not in ('movie', 'show', 'artist'):
|
||||
if mediatype not in ('movie', 'show', 'artist', 'photo'):
|
||||
return totalnodes
|
||||
|
||||
# Prevent duplicate for nodes of the same type
|
||||
|
@ -683,18 +679,20 @@ class LibrarySync(Thread):
|
|||
self.nodes = {
|
||||
'movie': [],
|
||||
'show': [],
|
||||
'artist': []
|
||||
'artist': [],
|
||||
'photo': []
|
||||
}
|
||||
self.playlists = {
|
||||
'movie': [],
|
||||
'show': [],
|
||||
'artist': []
|
||||
'artist': [],
|
||||
'photo': []
|
||||
}
|
||||
self.sorted_views = []
|
||||
|
||||
for view in sections:
|
||||
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.logMsg('Sorted views: %s' % self.sorted_views, 1)
|
||||
|
||||
|
@ -733,15 +731,15 @@ class LibrarySync(Thread):
|
|||
pass
|
||||
|
||||
# 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
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
# update views for all:
|
||||
self.views = emby_db.getAllViewInfo()
|
||||
self.logMsg("Removing views: %s" % self.old_views, 1)
|
||||
for view in self.old_views:
|
||||
emby_db.removeView(view)
|
||||
# update views for all:
|
||||
self.views = emby_db.getAllViewInfo()
|
||||
|
||||
self.logMsg("Finished processing views. Views saved: %s"
|
||||
% self.views, 1)
|
||||
|
@ -847,16 +845,15 @@ class LibrarySync(Thread):
|
|||
# Populate queue: GetMetadata
|
||||
for updateItem in self.updatelist:
|
||||
getMetadataQueue.put(updateItem)
|
||||
# Spawn GetMetadata threads for downloading
|
||||
# Spawn GetMetadata thread for downloading
|
||||
threads = []
|
||||
for i in range(min(self.syncThreadNumber, itemNumber)):
|
||||
thread = ThreadedGetMetadata(getMetadataQueue,
|
||||
processMetadataQueue,
|
||||
getMetadataLock,
|
||||
processMetadataLock)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
thread = ThreadedGetMetadata(getMetadataQueue,
|
||||
processMetadataQueue,
|
||||
getMetadataLock,
|
||||
processMetadataLock)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
self.logMsg("%s download threads spawned" % len(threads), 1)
|
||||
# Spawn one more thread to process Metadata, once downloaded
|
||||
thread = ThreadedProcessMetadata(processMetadataQueue,
|
||||
|
@ -1347,11 +1344,18 @@ class LibrarySync(Thread):
|
|||
# We haven't waited long enough for the PMS to finish
|
||||
# processing the item. Do it later
|
||||
continue
|
||||
if item['state'] == 5:
|
||||
if self.process_newitems(item) is True:
|
||||
deleteListe.append(i)
|
||||
elif item['state'] == 9:
|
||||
if self.process_deleteditems(item) is True:
|
||||
if item['state'] == 9:
|
||||
successful = self.process_deleteditems(item)
|
||||
else:
|
||||
successful = self.process_newitems(item)
|
||||
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)
|
||||
|
||||
# Get rid of the items we just processed
|
||||
|
@ -1423,15 +1427,23 @@ class LibrarySync(Thread):
|
|||
"processing queue" for later
|
||||
"""
|
||||
for item in data:
|
||||
state = item.get('state')
|
||||
typus = item.get('type')
|
||||
if state == 9 or (state == 5 and typus in (1, 4, 10)):
|
||||
self.itemsToProcess.append({
|
||||
'state': state,
|
||||
'type': typus,
|
||||
'ratingKey': item.get('itemID'),
|
||||
'timestamp': utils.getUnixTimestamp()
|
||||
})
|
||||
state = item.get('state')
|
||||
if state == 9 or typus in (1, 4, 10):
|
||||
itemId = item.get('itemID')
|
||||
# Have we already added this element?
|
||||
for existingItem in self.itemsToProcess:
|
||||
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):
|
||||
"""
|
||||
|
@ -1508,14 +1520,19 @@ class LibrarySync(Thread):
|
|||
userdata = API.getUserData()
|
||||
currSess['duration'] = userdata['Runtime']
|
||||
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
|
||||
items.append({
|
||||
'ratingKey': ratingKey,
|
||||
'kodi_id': kodiInfo[0],
|
||||
'file_id': kodiInfo[1],
|
||||
'kodi_type': kodiInfo[4],
|
||||
'viewOffset': PF.ConvertPlexToKodiTime(
|
||||
item.get('viewOffset')),
|
||||
'viewOffset': resume,
|
||||
'state': state,
|
||||
'duration': currSess['duration'],
|
||||
'viewCount': currSess['viewCount'],
|
||||
|
@ -1536,7 +1553,7 @@ class LibrarySync(Thread):
|
|||
try:
|
||||
self.run_internal()
|
||||
except Exception as e:
|
||||
utils.window('emby_dbScan', clear=True)
|
||||
utils.window('plex_dbScan', clear=True)
|
||||
self.logMsg('LibrarySync thread crashed', -1)
|
||||
self.logMsg('Error message: %s' % e, -1)
|
||||
import traceback
|
||||
|
@ -1575,6 +1592,9 @@ class LibrarySync(Thread):
|
|||
|
||||
log("---===### Starting LibrarySync ###===---", 0)
|
||||
|
||||
# Ensure that DBs exist if called for very first time
|
||||
self.initializeDBs()
|
||||
|
||||
if self.enableMusic:
|
||||
utils.advancedSettingsXML()
|
||||
|
||||
|
@ -1589,10 +1609,10 @@ class LibrarySync(Thread):
|
|||
return
|
||||
xbmc.sleep(1000)
|
||||
|
||||
if (window('emby_dbCheck') != "true" and installSyncDone):
|
||||
if (window('plex_dbCheck') != "true" and installSyncDone):
|
||||
# Verify the validity of the database
|
||||
currentVersion = settings('dbCreatedWithVersion')
|
||||
minVersion = window('emby_minDBVersion')
|
||||
minVersion = window('plex_minDBVersion')
|
||||
|
||||
if not self.compareDBVersion(currentVersion, minVersion):
|
||||
log("Db version out of date: %s minimum version required: "
|
||||
|
@ -1609,7 +1629,7 @@ class LibrarySync(Thread):
|
|||
utils.reset()
|
||||
break
|
||||
|
||||
window('emby_dbCheck', value="true")
|
||||
window('plex_dbCheck', value="true")
|
||||
|
||||
if not startupComplete:
|
||||
# Also runs when first installed
|
||||
|
@ -1619,15 +1639,15 @@ class LibrarySync(Thread):
|
|||
# Database does not exists
|
||||
log("The current Kodi version is incompatible "
|
||||
"to know which Kodi versions are supported.", -1)
|
||||
log('Current Kodi version: %s' % xbmc.getInfoLabel(
|
||||
'System.BuildVersion').decode('utf-8'))
|
||||
log('Current Kodi version: %s' % utils.tryDecode(
|
||||
xbmc.getInfoLabel('System.BuildVersion')))
|
||||
# "Current Kodi version is unsupported, cancel lib sync"
|
||||
self.dialog.ok(heading=self.addonName,
|
||||
line1=string(39403))
|
||||
break
|
||||
|
||||
# Run start up sync
|
||||
window('emby_dbScan', value="true")
|
||||
window('plex_dbScan', value="true")
|
||||
log("Db version: %s" % settings('dbCreatedWithVersion'), 0)
|
||||
lastTimeSync = utils.getUnixTimestamp()
|
||||
self.syncPMStime()
|
||||
|
@ -1635,7 +1655,7 @@ class LibrarySync(Thread):
|
|||
lastSync = utils.getUnixTimestamp()
|
||||
librarySync = fullSync()
|
||||
# Initialize time offset Kodi - PMS
|
||||
window('emby_dbScan', clear=True)
|
||||
window('plex_dbScan', clear=True)
|
||||
if librarySync:
|
||||
log("Initial start-up full sync successful", 0)
|
||||
startupComplete = True
|
||||
|
@ -1655,23 +1675,23 @@ class LibrarySync(Thread):
|
|||
break
|
||||
|
||||
# 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
|
||||
if window('plex_runLibScan') in ("full", "repair"):
|
||||
log('Full library scan requested, starting', 0)
|
||||
window('emby_dbScan', value="true")
|
||||
window('plex_dbScan', value="true")
|
||||
if window('plex_runLibScan') == "full":
|
||||
fullSync()
|
||||
elif window('plex_runLibScan') == "repair":
|
||||
fullSync(repair=True)
|
||||
window('plex_runLibScan', clear=True)
|
||||
window('emby_dbScan', clear=True)
|
||||
window('plex_dbScan', clear=True)
|
||||
# Full library sync finished
|
||||
self.showKodiNote(string(39407), forced=True)
|
||||
self.showKodiNote(string(39407), forced=False)
|
||||
# Reset views was requested from somewhere else
|
||||
elif window('plex_runLibScan') == "views":
|
||||
log('Refresh playlist and nodes requested, starting', 0)
|
||||
window('emby_dbScan', value="true")
|
||||
window('plex_dbScan', value="true")
|
||||
window('plex_runLibScan', clear=True)
|
||||
|
||||
# First remove playlists
|
||||
|
@ -1691,36 +1711,36 @@ class LibrarySync(Thread):
|
|||
self.showKodiNote(string(39406),
|
||||
forced=True,
|
||||
icon="error")
|
||||
window('emby_dbScan', clear=True)
|
||||
window('plex_dbScan', clear=True)
|
||||
else:
|
||||
now = utils.getUnixTimestamp()
|
||||
if (now - lastSync > fullSyncInterval and
|
||||
not xbmcplayer.isPlaying()):
|
||||
lastSync = now
|
||||
log('Doing scheduled full library scan', 1)
|
||||
window('emby_dbScan', value="true")
|
||||
window('plex_dbScan', value="true")
|
||||
if fullSync() is False and not threadStopped():
|
||||
log('Could not finish scheduled full sync', -1)
|
||||
self.showKodiNote(string(39410),
|
||||
forced=True,
|
||||
icon='error')
|
||||
window('emby_dbScan', clear=True)
|
||||
window('plex_dbScan', clear=True)
|
||||
# Full library sync finished
|
||||
self.showKodiNote(string(39407), forced=False)
|
||||
elif now - lastTimeSync > oneDay:
|
||||
lastTimeSync = now
|
||||
log('Starting daily time sync', 0)
|
||||
window('emby_dbScan', value="true")
|
||||
window('plex_dbScan', value="true")
|
||||
self.syncPMStime()
|
||||
window('emby_dbScan', clear=True)
|
||||
window('plex_dbScan', clear=True)
|
||||
elif enableBackgroundSync:
|
||||
# Check back whether we should process something
|
||||
# Only do this once every 10 seconds
|
||||
if now - lastProcessing > 10:
|
||||
lastProcessing = now
|
||||
window('emby_dbScan', value="true")
|
||||
window('plex_dbScan', value="true")
|
||||
processItems()
|
||||
window('emby_dbScan', clear=True)
|
||||
window('plex_dbScan', clear=True)
|
||||
# See if there is a PMS message we need to handle
|
||||
try:
|
||||
message = queue.get(block=False)
|
||||
|
@ -1729,10 +1749,10 @@ class LibrarySync(Thread):
|
|||
continue
|
||||
# Got a message from PMS; process it
|
||||
else:
|
||||
window('emby_dbScan', value="true")
|
||||
window('plex_dbScan', value="true")
|
||||
processMessage(message)
|
||||
queue.task_done()
|
||||
window('emby_dbScan', clear=True)
|
||||
window('plex_dbScan', clear=True)
|
||||
# NO sleep!
|
||||
continue
|
||||
else:
|
||||
|
|
|
@ -32,7 +32,7 @@ def getRealFileName(filename, isTemp=False):
|
|||
if os.path.supports_unicode_filenames:
|
||||
checkfile = filename
|
||||
else:
|
||||
checkfile = filename.encode("utf-8")
|
||||
checkfile = utils.tryEncode(filename)
|
||||
|
||||
# determine if our python module is able to access the file directly...
|
||||
if os.path.exists(checkfile):
|
||||
|
@ -46,7 +46,7 @@ def getRealFileName(filename, isTemp=False):
|
|||
else: filepart = filename.split("\\")[-1]
|
||||
tempfile = "special://temp/"+filepart
|
||||
xbmcvfs.copy(filename, tempfile)
|
||||
filename = xbmc.translatePath(tempfile).decode("utf-8")
|
||||
filename = utils.tryDecode(xbmc.translatePath(tempfile))
|
||||
|
||||
return (isTemp,filename)
|
||||
|
||||
|
@ -65,14 +65,14 @@ def getEmbyRatingFromKodiRating(rating):
|
|||
if (rating >= 5): favourite = True
|
||||
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()
|
||||
|
||||
previous_values = None
|
||||
filename = API.getFilePath()
|
||||
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.
|
||||
if enableimportsongrating:
|
||||
|
@ -83,7 +83,7 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei
|
|||
hasEmbeddedCover = False
|
||||
|
||||
|
||||
emby_dbitem = emby_db.getItem_byId(embyid)
|
||||
emby_dbitem = emby_db.getItem_byId(plexid)
|
||||
try:
|
||||
kodiid = emby_dbitem[0]
|
||||
except TypeError:
|
||||
|
@ -100,44 +100,44 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei
|
|||
if file_rating is None and currentvalue:
|
||||
return (currentvalue, comment, False)
|
||||
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
|
||||
updateEmbyRating = False
|
||||
|
||||
if currentvalue != None:
|
||||
# we need to translate the emby values...
|
||||
if emby_rating == 1 and currentvalue == 2:
|
||||
emby_rating = 2
|
||||
if emby_rating == 3 and currentvalue == 4:
|
||||
emby_rating = 4
|
||||
if plex_rating == 1 and currentvalue == 2:
|
||||
plex_rating = 2
|
||||
if plex_rating == 3 and currentvalue == 4:
|
||||
plex_rating = 4
|
||||
|
||||
#if updating rating into file is disabled, we ignore the rating in the file...
|
||||
if not enableupdatesongrating:
|
||||
file_rating = currentvalue
|
||||
#if convert emby likes/favourites convert to song rating is disabled, we ignore the emby rating...
|
||||
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
|
||||
rating = currentvalue
|
||||
updateFileRating = 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
|
||||
rating = emby_rating
|
||||
rating = plex_rating
|
||||
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
|
||||
rating = file_rating
|
||||
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...
|
||||
if emby_rating > file_rating:
|
||||
rating = emby_rating
|
||||
if plex_rating > file_rating:
|
||||
rating = plex_rating
|
||||
updateFileRating = True
|
||||
else:
|
||||
rating = file_rating
|
||||
|
@ -152,16 +152,16 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei
|
|||
rating = file_rating
|
||||
#determine if we should also send the rating to emby server
|
||||
if enableexportsongrating:
|
||||
if emby_rating == 1 and file_rating == 2:
|
||||
emby_rating = 2
|
||||
if emby_rating == 3 and file_rating == 4:
|
||||
emby_rating = 4
|
||||
if emby_rating != file_rating:
|
||||
if plex_rating == 1 and file_rating == 2:
|
||||
plex_rating = 2
|
||||
if plex_rating == 3 and file_rating == 4:
|
||||
plex_rating = 4
|
||||
if plex_rating != file_rating:
|
||||
updateEmbyRating = True
|
||||
|
||||
elif enableexportsongrating:
|
||||
#set the initial rating to emby value
|
||||
rating = emby_rating
|
||||
rating = plex_rating
|
||||
|
||||
if updateFileRating and enableupdatesongrating:
|
||||
updateRatingToFile(rating, filename)
|
||||
|
@ -169,8 +169,8 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei
|
|||
if updateEmbyRating and enableexportsongrating:
|
||||
# sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
|
||||
like, favourite, deletelike = getEmbyRatingFromKodiRating(rating)
|
||||
utils.window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update
|
||||
emby.updateUserRating(embyid, like, favourite, deletelike)
|
||||
utils.window("ignore-update-%s" %plexid, "true") #set temp windows prop to ignore the update from webclient update
|
||||
emby.updateUserRating(plexid, like, favourite, deletelike)
|
||||
|
||||
return (rating, comment, hasEmbeddedCover)
|
||||
|
||||
|
@ -193,6 +193,7 @@ def getSongTags(file):
|
|||
if pic.type == 3 and pic.data:
|
||||
#the file has an embedded cover
|
||||
hasEmbeddedCover = True
|
||||
break
|
||||
if audio.get("rating"):
|
||||
rating = float(audio.get("rating")[0])
|
||||
#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]
|
||||
tempfile = "special://temp/"+filepart
|
||||
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))
|
||||
|
||||
|
|
|
@ -40,11 +40,13 @@ class PlaybackUtils():
|
|||
|
||||
self.artwork = artwork.Artwork()
|
||||
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):
|
||||
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
settings = utils.settings
|
||||
|
||||
|
@ -56,7 +58,7 @@ class PlaybackUtils():
|
|||
listitem = xbmcgui.ListItem()
|
||||
playutils = putils.PlayUtils(item[0])
|
||||
|
||||
log("Play called.", 1)
|
||||
self.logMsg("Play called.", 1)
|
||||
playurl = playutils.getPlayUrl()
|
||||
if not playurl:
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
||||
|
@ -72,18 +74,18 @@ class PlaybackUtils():
|
|||
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||
'{server}%s' % item[0][0][0].attrib.get('key'))
|
||||
if xml in (None, 401):
|
||||
log('Could not download %s'
|
||||
self.logMsg('Could not download %s'
|
||||
% item[0][0][0].attrib.get('key'), -1)
|
||||
return xbmcplugin.setResolvedUrl(
|
||||
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')
|
||||
|
||||
playmethod = window('emby_%s.playmethod' % playurl)
|
||||
if playmethod == "Transcode":
|
||||
window('emby_%s.playmethod' % playurl, clear=True)
|
||||
playurl = playutils.audioSubsPref(
|
||||
listitem, playurl.decode('utf-8')).encode('utf-8')
|
||||
playurl = utils.tryEncode(playutils.audioSubsPref(
|
||||
listitem, utils.tryDecode(playurl)))
|
||||
window('emby_%s.playmethod' % playurl, "Transcode")
|
||||
listitem.setPath(playurl)
|
||||
self.setProperties(playurl, listitem)
|
||||
|
@ -92,18 +94,18 @@ class PlaybackUtils():
|
|||
############### ORGANIZE CURRENT PLAYLIST ################
|
||||
|
||||
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
|
||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
playlist = self.pl.playlist
|
||||
startPos = max(playlist.getposition(), 0) # Can return -1
|
||||
sizePlaylist = playlist.size()
|
||||
self.currentPosition = startPos
|
||||
|
||||
propertiesPlayback = window('emby_playbackProps') == "true"
|
||||
propertiesPlayback = window('plex_playbackProps') == "true"
|
||||
introsPlaylist = False
|
||||
dummyPlaylist = False
|
||||
|
||||
log("Playlist start position: %s" % startPos, 1)
|
||||
log("Playlist plugin position: %s" % self.currentPosition, 1)
|
||||
log("Playlist size: %s" % sizePlaylist, 1)
|
||||
self.logMsg("Playlist start position: %s" % startPos, 2)
|
||||
self.logMsg("Playlist plugin position: %s" % self.currentPosition, 2)
|
||||
self.logMsg("Playlist size: %s" % sizePlaylist, 2)
|
||||
|
||||
############### RESUME POINT ################
|
||||
|
||||
|
@ -113,12 +115,12 @@ class PlaybackUtils():
|
|||
# Otherwise we get a loop.
|
||||
if not propertiesPlayback:
|
||||
|
||||
window('emby_playbackProps', value="true")
|
||||
log("Setting up properties in playlist.", 1)
|
||||
window('plex_playbackProps', value="true")
|
||||
self.logMsg("Setting up properties in playlist.", 1)
|
||||
|
||||
if (not homeScreen and not seektime and
|
||||
window('emby_customPlaylist') != "true"):
|
||||
log("Adding dummy file to playlist.", 2)
|
||||
window('plex_customplaylist') != "true"):
|
||||
self.logMsg("Adding dummy file to playlist.", 2)
|
||||
dummyPlaylist = True
|
||||
playlist.add(playurl, listitem, index=startPos)
|
||||
# Remove the original item from playlist
|
||||
|
@ -145,7 +147,7 @@ class PlaybackUtils():
|
|||
if homeScreen and not seektime and not sizePlaylist:
|
||||
# Extend our current playlist with the actual item to play
|
||||
# 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(
|
||||
dbid,
|
||||
PF.GetKodiTypeFromPlex(API.getType()))
|
||||
|
@ -166,7 +168,7 @@ class PlaybackUtils():
|
|||
additionalListItem = xbmcgui.ListItem()
|
||||
additionalPlayurl = playutils.getPlayUrl(
|
||||
partNumber=counter)
|
||||
log("Adding additional part: %s" % counter, 1)
|
||||
self.logMsg("Adding additional part: %s" % counter, 1)
|
||||
|
||||
self.setProperties(additionalPlayurl, additionalListItem)
|
||||
self.setArtwork(additionalListItem)
|
||||
|
@ -181,14 +183,14 @@ class PlaybackUtils():
|
|||
if dummyPlaylist:
|
||||
# Added a dummy file to the playlist,
|
||||
# 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)
|
||||
|
||||
|
||||
# We just skipped adding properties. Reset flag for next time.
|
||||
elif propertiesPlayback:
|
||||
log("Resetting properties playback flag.", 2)
|
||||
window('emby_playbackProps', clear=True)
|
||||
self.logMsg("Resetting properties playback flag.", 2)
|
||||
window('plex_playbackProps', clear=True)
|
||||
|
||||
#self.pl.verifyPlaylist()
|
||||
########## SETUP MAIN ITEM ##########
|
||||
|
@ -196,8 +198,8 @@ class PlaybackUtils():
|
|||
# For transcoding only, ask for audio/subs pref
|
||||
if window('emby_%s.playmethod' % playurl) == "Transcode":
|
||||
window('emby_%s.playmethod' % playurl, clear=True)
|
||||
playurl = playutils.audioSubsPref(
|
||||
listitem, playurl.decode('utf-8')).encode('utf-8')
|
||||
playurl = utils.tryEncode(playutils.audioSubsPref(
|
||||
listitem, utils.tryDecode(playurl)))
|
||||
window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||
|
||||
listitem.setPath(playurl)
|
||||
|
@ -205,19 +207,19 @@ class PlaybackUtils():
|
|||
|
||||
############### PLAYBACK ################
|
||||
|
||||
if homeScreen and seektime and window('emby_customPlaylist') != "true":
|
||||
log("Play as a widget item.", 1)
|
||||
if homeScreen and seektime and window('plex_customplaylist') != "true":
|
||||
self.logMsg("Play as a widget item.", 1)
|
||||
API.CreateListItemFromPlexItem(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)):
|
||||
# Playlist was created just now, play it.
|
||||
log("Play playlist.", 1)
|
||||
self.logMsg("Play playlist.", 1)
|
||||
xbmc.Player().play(playlist, startpos=startPos)
|
||||
|
||||
else:
|
||||
log("Play as a regular item.", 1)
|
||||
self.logMsg("Play as a regular item.", 1)
|
||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
|
||||
def AddTrailers(self, xml):
|
||||
|
@ -268,17 +270,13 @@ class PlaybackUtils():
|
|||
# Set all properties necessary for plugin path playback
|
||||
itemid = self.API.getRatingKey()
|
||||
itemtype = self.API.getType()
|
||||
resume, runtime = self.API.getRuntime()
|
||||
userdata = self.API.getUserData()
|
||||
|
||||
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.itemid' % embyitem, value=itemid)
|
||||
|
||||
# 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())
|
||||
window('%s.playcount' % embyitem, value=str(userdata['PlayCount']))
|
||||
|
||||
if itemtype == "episode":
|
||||
window('%s.refreshid' % embyitem,
|
||||
|
|
|
@ -10,8 +10,8 @@ import xbmcgui
|
|||
import utils
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
|
||||
from urllib import urlencode
|
||||
import embydb_functions as embydb
|
||||
import kodidb_functions as kodidb
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -32,17 +32,10 @@ class Player(xbmc.Player):
|
|||
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
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.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)
|
||||
self.logMsg("Started playback monitor.", 2)
|
||||
|
||||
def GetPlayStats(self):
|
||||
return self.playStats
|
||||
|
@ -51,15 +44,13 @@ class Player(xbmc.Player):
|
|||
"""
|
||||
Window values need to have been set in Kodimonitor.py
|
||||
"""
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
# Will be called when xbmc starts playing a file
|
||||
xbmcplayer = self.xbmcplayer
|
||||
self.stopAll()
|
||||
|
||||
# Get current file (in utf-8!)
|
||||
try:
|
||||
currentFile = xbmcplayer.getPlayingFile()
|
||||
currentFile = self.getPlayingFile()
|
||||
xbmc.sleep(300)
|
||||
except:
|
||||
currentFile = ""
|
||||
|
@ -67,53 +58,65 @@ class Player(xbmc.Player):
|
|||
while not currentFile:
|
||||
xbmc.sleep(100)
|
||||
try:
|
||||
currentFile = xbmcplayer.getPlayingFile()
|
||||
currentFile = self.getPlayingFile()
|
||||
except:
|
||||
pass
|
||||
if count == 20:
|
||||
log("Cancelling playback report...", 1)
|
||||
self.logMsg("Cancelling playback report...", 1)
|
||||
break
|
||||
else:
|
||||
count += 1
|
||||
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
|
||||
|
||||
# Save currentFile for cleanup later and for references
|
||||
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
|
||||
itemId = window("emby_%s.itemid" % currentFile)
|
||||
count = 0
|
||||
while not itemId:
|
||||
xbmc.sleep(200)
|
||||
itemId = window("emby_%s.itemid" % currentFile)
|
||||
# try 20 times or about 10 seconds
|
||||
if count == 20:
|
||||
log("Could not find itemId, cancelling playback report...", -1)
|
||||
if count == 5:
|
||||
self.logMsg("Could not find itemId, cancelling playback "
|
||||
"report!", -1)
|
||||
return
|
||||
count += 1
|
||||
|
||||
log("ONPLAYBACK_STARTED: %s itemid: %s"
|
||||
% (currentFile.decode('utf-8'), itemId), 0)
|
||||
self.logMsg("ONPLAYBACK_STARTED: %s itemid: %s"
|
||||
% (utils.tryDecode(currentFile), itemId), 0)
|
||||
|
||||
embyitem = "emby_%s" % currentFile
|
||||
runtime = window("%s.runtime" % embyitem)
|
||||
refresh_id = window("%s.refreshid" % embyitem)
|
||||
playMethod = window("%s.playmethod" % embyitem)
|
||||
itemType = window("%s.type" % embyitem)
|
||||
try:
|
||||
playcount = int(window("%s.playcount" % embyitem))
|
||||
except ValueError:
|
||||
playcount = 0
|
||||
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')
|
||||
if (window('emby_customPlaylist') == "true" and customseek):
|
||||
customseek = window('plex_customplaylist.seektime')
|
||||
if customseek:
|
||||
# Start at, when using custom playlist (play to Kodi from webclient)
|
||||
log("Seeking to: %s" % customseek, 1)
|
||||
xbmcplayer.seekTime(int(customseek))
|
||||
window('emby_customPlaylist.seektime', clear=True)
|
||||
self.logMsg("Seeking to: %s" % customseek, 1)
|
||||
try:
|
||||
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
|
||||
volume_query = {
|
||||
|
@ -193,7 +196,8 @@ class Player(xbmc.Player):
|
|||
|
||||
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)
|
||||
|
||||
if externalIndex.get(str(indexSubs)):
|
||||
|
@ -218,18 +222,24 @@ class Player(xbmc.Player):
|
|||
try:
|
||||
runtime = int(runtime)
|
||||
except ValueError:
|
||||
runtime = xbmcplayer.getTotalTime()
|
||||
log("Runtime is missing, Kodi runtime: %s" % runtime, 1)
|
||||
try:
|
||||
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')
|
||||
playQueueID = window('playQueueID')
|
||||
playQueueItemID = window('plex_%s.playQueueItemID' % currentFile)
|
||||
with embydb.GetEmbyDB() as emby_db:
|
||||
emby_dbitem = emby_db.getItem_byId(itemId)
|
||||
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
|
||||
data = {
|
||||
'playQueueVersion': playQueueVersion,
|
||||
'playQueueID': playQueueID,
|
||||
'playQueueItemID': playQueueItemID,
|
||||
'runtime': runtime * 1000,
|
||||
'runtime': runtime,
|
||||
'item_id': itemId,
|
||||
'refresh_id': refresh_id,
|
||||
'currentfile': currentFile,
|
||||
|
@ -237,11 +247,14 @@ class Player(xbmc.Player):
|
|||
'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
|
||||
'playmethod': playMethod,
|
||||
'Type': itemType,
|
||||
'currentPosition': int(seekTime)
|
||||
'currentPosition': int(seekTime),
|
||||
'fileid': fileid,
|
||||
'itemType': itemType,
|
||||
'playcount': playcount
|
||||
}
|
||||
|
||||
self.played_info[currentFile] = data
|
||||
log("ADDING_FILE: %s" % self.played_info, 1)
|
||||
self.logMsg("ADDING_FILE: %s" % data, 1)
|
||||
|
||||
# log some playback stats
|
||||
'''if(itemType != None):
|
||||
|
@ -258,199 +271,48 @@ class Player(xbmc.Player):
|
|||
else:
|
||||
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):
|
||||
|
||||
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):
|
||||
self.played_info[currentFile]['paused'] = True
|
||||
|
||||
self.reportPlayback()
|
||||
|
||||
def onPlayBackResumed(self):
|
||||
|
||||
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):
|
||||
self.played_info[currentFile]['paused'] = False
|
||||
|
||||
self.reportPlayback()
|
||||
|
||||
def onPlayBackSeek(self, time, seekOffset):
|
||||
# Make position when seeking a bit more accurate
|
||||
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):
|
||||
position = self.xbmcplayer.getTime()
|
||||
try:
|
||||
position = self.getTime()
|
||||
except RuntimeError:
|
||||
# When Kodi is not playing
|
||||
return
|
||||
self.played_info[currentFile]['currentPosition'] = position
|
||||
|
||||
self.reportPlayback()
|
||||
|
||||
def onPlayBackStopped(self):
|
||||
# Will be called when user stops xbmc playing a file
|
||||
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
log("ONPLAYBACK_STOPPED", 1)
|
||||
self.logMsg("ONPLAYBACK_STOPPED", 1)
|
||||
|
||||
self.stopAll()
|
||||
|
||||
window('Plex_currently_playing_itemid', clear=True)
|
||||
window('emby_customPlaylist', clear=True)
|
||||
window('emby_customPlaylist.seektime', clear=True)
|
||||
window('emby_customPlaylist.seektime', clear=True)
|
||||
log("Clear playlist properties.", 1)
|
||||
window('plex_customplaylist', clear=True)
|
||||
window('plex_customplaylist.seektime', clear=True)
|
||||
window('plex_customplaylist.seektime', clear=True)
|
||||
self.logMsg("Clear playlist properties.", 1)
|
||||
|
||||
def onPlayBackEnded(self):
|
||||
# Will be called when xbmc stops playing a file, because the file ended
|
||||
|
@ -459,31 +321,28 @@ class Player(xbmc.Player):
|
|||
|
||||
def stopAll(self):
|
||||
|
||||
log = self.logMsg
|
||||
lang = utils.language
|
||||
settings = utils.settings
|
||||
|
||||
doUtils = self.doUtils
|
||||
|
||||
if not self.played_info:
|
||||
return
|
||||
|
||||
log("Played_information: %s" % self.played_info, 1)
|
||||
self.logMsg("Played_information: %s" % self.played_info, 1)
|
||||
# Process each items
|
||||
for item in self.played_info:
|
||||
|
||||
data = self.played_info.get(item)
|
||||
if data:
|
||||
|
||||
log("Item path: %s" % item, 2)
|
||||
log("Item data: %s" % data, 2)
|
||||
self.logMsg("Item path: %s" % item, 2)
|
||||
self.logMsg("Item data: %s" % data, 2)
|
||||
|
||||
runtime = data['runtime']
|
||||
currentPosition = data['currentPosition']
|
||||
itemid = data['item_id']
|
||||
refresh_id = data['refresh_id']
|
||||
currentFile = data['currentfile']
|
||||
type = data['Type']
|
||||
media_type = data['Type']
|
||||
playMethod = data['playmethod']
|
||||
|
||||
# Prevent manually mark as watched in Kodi monitor
|
||||
|
@ -491,21 +350,31 @@ class Player(xbmc.Player):
|
|||
|
||||
if currentPosition and runtime:
|
||||
try:
|
||||
percentComplete = currentPosition / int(runtime)
|
||||
percentComplete = float(currentPosition) / float(runtime)
|
||||
except ZeroDivisionError:
|
||||
# Runtime is 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.
|
||||
offerDelete = False
|
||||
|
||||
if type == "Episode" and settings('deleteTV') == "true":
|
||||
if media_type == "Episode" and settings('deleteTV') == "true":
|
||||
offerDelete = True
|
||||
elif type == "Movie" and settings('deleteMovies') == "true":
|
||||
elif media_type == "Movie" and settings('deleteMovies') == "true":
|
||||
offerDelete = True
|
||||
|
||||
if settings('offerDelete') != "true":
|
||||
|
@ -514,19 +383,18 @@ class Player(xbmc.Player):
|
|||
|
||||
# Plex: never delete
|
||||
offerDelete = False
|
||||
if percentComplete >= markPlayedAt and offerDelete:
|
||||
if percentComplete >= markPlayed and offerDelete:
|
||||
resp = xbmcgui.Dialog().yesno(
|
||||
lang(30091),
|
||||
lang(33015),
|
||||
autoclose=120000)
|
||||
if not resp:
|
||||
log("User skipped deletion.", 1)
|
||||
self.logMsg("User skipped deletion.", 1)
|
||||
continue
|
||||
|
||||
url = "{server}/emby/Items/%s?format=json" % itemid
|
||||
log("Deleting request: %s" % itemid, 1)
|
||||
doUtils(url, type="DELETE")
|
||||
self.stopPlayback(data)
|
||||
self.logMsg("Deleting request: %s" % itemid, 1)
|
||||
self.doUtils(url, action_type="DELETE")
|
||||
|
||||
# Clean the WINDOW properties
|
||||
for filename in self.played_info:
|
||||
|
@ -536,35 +404,18 @@ class Player(xbmc.Player):
|
|||
'emby_%s.refreshid' % filename,
|
||||
'emby_%s.playmethod' % filename,
|
||||
'emby_%s.type' % filename,
|
||||
'plex_%s.playQueueItemID' % filename,
|
||||
'plex_%s.playlistPosition' % filename,
|
||||
'plex_%s.guid' % filename
|
||||
'emby_%s.runtime' % filename,
|
||||
'emby_%s.playcount' % filename,
|
||||
'plex_%s.playlistPosition' % filename
|
||||
)
|
||||
for item in cleanup:
|
||||
utils.window(item, clear=True)
|
||||
|
||||
# Stop transcoding
|
||||
if playMethod == "Transcode":
|
||||
log("Transcoding for %s terminating" % itemid, 1)
|
||||
doUtils(
|
||||
self.logMsg("Transcoding for %s terminating" % itemid, 1)
|
||||
self.doUtils(
|
||||
"{server}/video/:/transcode/universal/stop",
|
||||
parameters={'session': self.clientInfo.getDeviceId()})
|
||||
|
||||
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
|
||||
from urllib import urlencode
|
||||
from threading import Lock
|
||||
from functools import wraps
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
import embydb_functions as embydb
|
||||
import read_embyserver as embyserver
|
||||
import utils
|
||||
import playbackutils
|
||||
import PlexFunctions
|
||||
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
|
||||
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.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):
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
@lockMethod.decorate
|
||||
def Typus(self, value=None):
|
||||
if value:
|
||||
self.typus = value
|
||||
else:
|
||||
return self.typus
|
||||
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
@lockMethod.decorate
|
||||
def PlayQueueVersion(self, value=None):
|
||||
if value:
|
||||
self.playQueueVersion = value
|
||||
else:
|
||||
return self.playQueueVersion
|
||||
|
||||
player = xbmc.Player()
|
||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
playlist.clear()
|
||||
@lockMethod.decorate
|
||||
def QueueId(self, value=None):
|
||||
if value:
|
||||
self.queueId = value
|
||||
else:
|
||||
return self.queueId
|
||||
|
||||
log("---*** PLAY ALL ***---", 1)
|
||||
log("Items: %s and start at: %s" % (itemids, startat), 1)
|
||||
@lockMethod.decorate
|
||||
def Guid(self, value=None):
|
||||
if value:
|
||||
self.guid = value
|
||||
else:
|
||||
return self.guid
|
||||
|
||||
started = False
|
||||
window('emby_customplaylist', value="true")
|
||||
|
||||
if startat != 0:
|
||||
# Seek to the starting position
|
||||
window('emby_customplaylist.seektime', str(startat))
|
||||
@lockMethod.decorate
|
||||
def clear(self):
|
||||
"""
|
||||
Empties current Kodi playlist and associated variables
|
||||
"""
|
||||
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:
|
||||
for itemid in itemids:
|
||||
for item in self.items:
|
||||
itemid = item['plexId']
|
||||
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
|
||||
log("Item was not found in the database, manually adding item.", 1)
|
||||
self.logMsg('Couldnt find item %s in Kodi db'
|
||||
% itemid, 1)
|
||||
item = PlexFunctions.GetPlexMetadata(itemid)
|
||||
if item is None or item == 401:
|
||||
log('Could not download itemid %s' % itemid, -1)
|
||||
if item in (None, 401):
|
||||
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:
|
||||
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:
|
||||
# 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 not started:
|
||||
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)
|
||||
if startPlayer is True and len(self.playlist) > 0:
|
||||
if startpos is not None:
|
||||
self.player.play(self.playlist, startpos=startpos)
|
||||
else:
|
||||
# Add to playlist
|
||||
self.addtoPlaylist(dbid, mediatype)
|
||||
self.logMsg('Never received a starting item for playlist, '
|
||||
'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()
|
||||
embycursor.close()
|
||||
return playlist
|
||||
|
||||
startitem: tuple (typus, id), where typus is either
|
||||
'playQueueItemID' or 'plexId' and id is the corresponding
|
||||
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):
|
||||
"""
|
||||
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 = {
|
||||
|
||||
'jsonrpc': "2.0",
|
||||
'id': 1,
|
||||
'method': "Playlist.Add",
|
||||
'params': {
|
||||
|
||||
'playlistid': 1
|
||||
'playlistid': self.playlistId
|
||||
}
|
||||
}
|
||||
if dbid is not None:
|
||||
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
|
||||
pl['params']['item'] = {'%sid' % utils.tryEncode(mediatype):
|
||||
int(dbid)}
|
||||
else:
|
||||
pl['params']['item'] = {'file': url}
|
||||
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
|
||||
|
||||
result = xbmc.executeJSONRPC(json.dumps(pl))
|
||||
self.logMsg(result, 2)
|
||||
|
||||
def addtoPlaylist_xbmc(self, playlist, item):
|
||||
path = "plugin://plugin.video.plexkodiconnect.movies/"
|
||||
def _addtoPlaylist_xbmc(self, item):
|
||||
API = PlexAPI.API(item)
|
||||
params = {
|
||||
'mode': "play",
|
||||
'dbid': 999999999
|
||||
'dbid': 999999999,
|
||||
'id': API.getRatingKey(),
|
||||
'filename': API.getKey()
|
||||
}
|
||||
API = PlexAPI.API(item[0])
|
||||
params['id'] = API.getRatingKey()
|
||||
params['filename'] = API.getKey()
|
||||
playurl = path + '?' + urlencode(params)
|
||||
playurl = "plugin://plugin.video.plexkodiconnect.movies/?%s" \
|
||||
% 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):
|
||||
|
||||
pl = {
|
||||
|
@ -151,20 +294,23 @@ class Playlist():
|
|||
'method': "Playlist.Insert",
|
||||
'params': {
|
||||
|
||||
'playlistid': 1,
|
||||
'playlistid': self.playlistId,
|
||||
'position': position
|
||||
}
|
||||
}
|
||||
if dbid is not None:
|
||||
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
|
||||
pl['params']['item'] = {'%sid' % utils.tryEncode(mediatype):
|
||||
int(dbid)}
|
||||
else:
|
||||
pl['params']['item'] = {'file': url}
|
||||
|
||||
result = xbmc.executeJSONRPC(json.dumps(pl))
|
||||
self.logMsg(result, 2)
|
||||
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
|
||||
|
||||
@lockMethod.decorate
|
||||
def verifyPlaylist(self):
|
||||
self._verifyPlaylist()
|
||||
|
||||
def _verifyPlaylist(self):
|
||||
pl = {
|
||||
|
||||
'jsonrpc': "2.0",
|
||||
|
@ -172,13 +318,13 @@ class Playlist():
|
|||
'method': "Playlist.GetItems",
|
||||
'params': {
|
||||
|
||||
'playlistid': 1,
|
||||
'playlistid': self.playlistId,
|
||||
'properties': ['title', 'file']
|
||||
}
|
||||
}
|
||||
result = xbmc.executeJSONRPC(json.dumps(pl))
|
||||
self.logMsg(result, 2)
|
||||
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
|
||||
|
||||
@lockMethod.decorate
|
||||
def removefromPlaylist(self, position):
|
||||
|
||||
pl = {
|
||||
|
@ -188,9 +334,8 @@ class Playlist():
|
|||
'method': "Playlist.Remove",
|
||||
'params': {
|
||||
|
||||
'playlistid': 1,
|
||||
'playlistid': self.playlistId,
|
||||
'position': position
|
||||
}
|
||||
}
|
||||
result = xbmc.executeJSONRPC(json.dumps(pl))
|
||||
self.logMsg(result, 2)
|
||||
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
|
||||
|
|
|
@ -37,100 +37,82 @@ class PlayUtils():
|
|||
|
||||
playurl is utf-8 encoded!
|
||||
"""
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
|
||||
self.API.setPartNumber(partNumber)
|
||||
playurl = self.isDirectPlay()
|
||||
|
||||
if playurl:
|
||||
log("File is direct playing.", 1)
|
||||
playurl = playurl.encode('utf-8')
|
||||
if playurl is not None:
|
||||
self.logMsg("File is direct playing.", 1)
|
||||
playurl = utils.tryEncode(playurl)
|
||||
# Set playmethod property
|
||||
window('emby_%s.playmethod' % playurl, "DirectPlay")
|
||||
utils.window('emby_%s.playmethod' % playurl, "DirectPlay")
|
||||
|
||||
elif self.isDirectStream():
|
||||
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
|
||||
utils.window('emby_%s.playmethod' % playurl, "DirectStream")
|
||||
|
||||
elif self.isTranscoding():
|
||||
log("File is transcoding.", 1)
|
||||
quality = {
|
||||
'maxVideoBitrate': self.getBitrate(),
|
||||
'videoResolution': self.getResolution(),
|
||||
'videoQuality': '100'
|
||||
}
|
||||
playurl = self.API.getTranscodeVideoPath(
|
||||
else:
|
||||
self.logMsg("File is transcoding.", 1)
|
||||
playurl = utils.tryEncode(self.API.getTranscodeVideoPath(
|
||||
'Transcode',
|
||||
quality=quality).encode('utf-8')
|
||||
quality={
|
||||
'maxVideoBitrate': self.getBitrate(),
|
||||
'videoResolution': self.getResolution(),
|
||||
'videoQuality': '100'
|
||||
}))
|
||||
# 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
|
||||
|
||||
def httpPlay(self):
|
||||
# Audio, Video, Photo
|
||||
item = self.item
|
||||
server = self.server
|
||||
|
||||
itemid = item['Id']
|
||||
mediatype = item['MediaType']
|
||||
itemid = self.item['Id']
|
||||
mediatype = self.item['MediaType']
|
||||
|
||||
if mediatype == "Audio":
|
||||
playurl = "%s/emby/Audio/%s/stream" % (server, itemid)
|
||||
playurl = "%s/emby/Audio/%s/stream" % (self.server, itemid)
|
||||
else:
|
||||
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
|
||||
playurl = "%s/emby/Videos/%s/stream?static=true" % (self.server, itemid)
|
||||
|
||||
return playurl
|
||||
|
||||
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
|
||||
if self.API.shouldStream() is True:
|
||||
self.logMsg("Plex item optimized for direct streaming", 1)
|
||||
return False
|
||||
|
||||
return
|
||||
# set to either 'Direct Stream=1' or 'Transcode=2'
|
||||
# and NOT to 'Direct Play=0'
|
||||
if utils.settings('playType') != "0":
|
||||
# User forcing to play via HTTP
|
||||
self.logMsg("User chose to not direct play", 1)
|
||||
return False
|
||||
|
||||
if self.h265enabled():
|
||||
return False
|
||||
|
||||
path = self.API.validatePlayurl(self.API.getFilePath(),
|
||||
return
|
||||
if self.mustTranscode():
|
||||
return
|
||||
return self.API.validatePlayurl(self.API.getFilePath(),
|
||||
self.API.getType(),
|
||||
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):
|
||||
|
||||
item = self.item
|
||||
|
||||
try:
|
||||
playurl = item['MediaSources'][0]['Path']
|
||||
playurl = self.item['MediaSources'][0]['Path']
|
||||
except (IndexError, KeyError):
|
||||
playurl = item['Path']
|
||||
playurl = self.item['Path']
|
||||
|
||||
if item.get('VideoType'):
|
||||
if self.item.get('VideoType'):
|
||||
# Specific format modification
|
||||
type = item['VideoType']
|
||||
|
||||
if type == "Dvd":
|
||||
if self.item['VideoType'] == "Dvd":
|
||||
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
|
||||
elif type == "BluRay":
|
||||
elif self.item['VideoType'] == "BluRay":
|
||||
playurl = "%s/BDMV/index.bdmv" % playurl
|
||||
|
||||
# Assign network protocol
|
||||
|
@ -146,59 +128,71 @@ class PlayUtils():
|
|||
|
||||
def fileExists(self):
|
||||
|
||||
log = self.logMsg
|
||||
|
||||
if 'Path' not in self.item:
|
||||
# File has no path defined in server
|
||||
return False
|
||||
|
||||
# Convert path to direct play
|
||||
path = self.directPlay()
|
||||
log("Verifying path: %s" % path, 1)
|
||||
self.logMsg("Verifying path: %s" % path, 1)
|
||||
|
||||
if xbmcvfs.exists(path):
|
||||
log("Path exists.", 1)
|
||||
self.logMsg("Path exists.", 1)
|
||||
return True
|
||||
|
||||
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
|
||||
|
||||
else:
|
||||
log("Failed to find file.", 1)
|
||||
self.logMsg("Failed to find file.", 1)
|
||||
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()
|
||||
self.logMsg("videoCodec: %s" % videoCodec, 2)
|
||||
codec = videoCodec['videocodec']
|
||||
resolution = videoCodec['resolution']
|
||||
h265 = self.getH265()
|
||||
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)
|
||||
if (utils.settings('transcodeHi10P') == 'true' and
|
||||
videoCodec['bitDepth'] == '10'):
|
||||
self.logMsg('Option to transcode 10bit video content enabled.', 1)
|
||||
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
|
||||
|
||||
def isDirectStream(self):
|
||||
# Never transcode Music
|
||||
if self.API.getType() == 'track':
|
||||
return True
|
||||
# set to 'Transcode=2'
|
||||
if utils.settings('playType') == "2":
|
||||
# User forcing to play via HTTP
|
||||
self.logMsg("User chose to transcode", 1)
|
||||
return False
|
||||
if self.h265enabled():
|
||||
if self.mustTranscode():
|
||||
return False
|
||||
# Verify the bitrate
|
||||
if not self.isNetworkSufficient():
|
||||
|
@ -207,25 +201,6 @@ class PlayUtils():
|
|||
return False
|
||||
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):
|
||||
"""
|
||||
Returns True if the network is sufficient (set in file settings)
|
||||
|
@ -243,38 +218,6 @@ class PlayUtils():
|
|||
return False
|
||||
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):
|
||||
# get the addon video quality
|
||||
videoQuality = utils.settings('transcoderVideoQualities')
|
||||
|
@ -295,14 +238,19 @@ class PlayUtils():
|
|||
return bitrate.get(videoQuality, 2147483)
|
||||
|
||||
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 = {
|
||||
'0': None,
|
||||
'0': 9999999,
|
||||
'1': 480,
|
||||
'2': 720,
|
||||
'3': 1080
|
||||
}
|
||||
return H265.get(chosen, None)
|
||||
return H265[utils.settings('transcodeH265')]
|
||||
|
||||
def getResolution(self):
|
||||
chosen = utils.settings('transcoderVideoQualities')
|
||||
|
@ -365,7 +313,7 @@ class PlayUtils():
|
|||
|
||||
#audioStreamsChannelsList[audioNum] = stream.attrib['channels']
|
||||
audioStreamsList.append(index)
|
||||
audioStreams.append(track.encode('utf-8'))
|
||||
audioStreams.append(utils.tryEncode(track))
|
||||
audioNum += 1
|
||||
|
||||
# Subtitles
|
||||
|
@ -389,7 +337,7 @@ class PlayUtils():
|
|||
downloadableStreams.append(index)
|
||||
|
||||
subtitleStreamsList.append(index)
|
||||
subtitleStreams.append(track.encode('utf-8'))
|
||||
subtitleStreams.append(utils.tryEncode(track))
|
||||
subNum += 1
|
||||
|
||||
if audioNum > 1:
|
||||
|
@ -421,7 +369,7 @@ class PlayUtils():
|
|||
% (self.server, selectSubsIndex)
|
||||
url = self.API.addPlexHeadersToUrl(url)
|
||||
self.logMsg("Downloadable sub: %s: %s" % (selectSubsIndex, url), 1)
|
||||
listitem.setSubtitles([url.encode('utf-8')])
|
||||
listitem.setSubtitles([utils.tryEncode(url)])
|
||||
else:
|
||||
self.logMsg('Need to burn in subtitle %s' % selectSubsIndex, 1)
|
||||
playurlprefs["subtitleStreamID"] = selectSubsIndex
|
||||
|
|
|
@ -5,6 +5,7 @@ import string
|
|||
import xbmc
|
||||
|
||||
from utils import logging
|
||||
import embydb_functions as embydb
|
||||
|
||||
|
||||
def xbmc_photo():
|
||||
|
@ -58,7 +59,9 @@ def getOKMsg():
|
|||
|
||||
|
||||
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):
|
||||
|
@ -69,7 +72,10 @@ def millisToTime(t):
|
|||
seconds = seconds % 60
|
||||
minutes = minutes % 60
|
||||
millis = millis % 1000
|
||||
return {'hours':hours,'minutes':minutes,'seconds':seconds,'milliseconds':millis}
|
||||
return {'hours': hours,
|
||||
'minutes': minutes,
|
||||
'seconds': seconds,
|
||||
'milliseconds': millis}
|
||||
|
||||
|
||||
def textFromXml(element):
|
||||
|
@ -86,44 +92,70 @@ class jsonClass():
|
|||
def jsonrpc(self, action, arguments={}):
|
||||
""" put some JSON together for the JSON-RPC APIv6 """
|
||||
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":
|
||||
request=json.dumps({ "jsonrpc" : "2.0",
|
||||
"id" : 1 ,
|
||||
"method" : "JSONRPC.Ping" })
|
||||
elif action.lower() == "playmedia":
|
||||
xbmc.Player().play("plugin://plugin.video.plexkodiconnect/"
|
||||
"?mode=companion&arguments=%s"
|
||||
% arguments)
|
||||
return True
|
||||
request = json.dumps({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "JSONRPC.Ping"
|
||||
})
|
||||
elif arguments:
|
||||
request=json.dumps({ "id" : 1,
|
||||
"jsonrpc" : "2.0",
|
||||
"method" : action,
|
||||
"params" : arguments})
|
||||
request = json.dumps({
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"method": action,
|
||||
"params": arguments})
|
||||
else:
|
||||
request=json.dumps({ "id" : 1,
|
||||
"jsonrpc" : "2.0",
|
||||
"method" : action})
|
||||
request = json.dumps({
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"method": action
|
||||
})
|
||||
|
||||
result = self.parseJSONRPC(xbmc.executeJSONRPC(request))
|
||||
|
||||
if not result and self.settings['webserver_enabled']:
|
||||
# xbmc.executeJSONRPC appears to fail on the login screen, but going
|
||||
# through the network stack works, so let's try the request again
|
||||
# xbmc.executeJSONRPC appears to fail on the login screen, but
|
||||
# going through the network stack works, so let's try the request
|
||||
# again
|
||||
result = self.parseJSONRPC(self.requestMgr.post(
|
||||
"127.0.0.1",
|
||||
self.settings['port'],
|
||||
"/jsonrpc",
|
||||
request,
|
||||
{ 'Content-Type' : 'application/json',
|
||||
'Authorization' : 'Basic ' + string.strip(base64.encodestring(self.settings['user'] + ':' + self.settings['passwd'])) }))
|
||||
|
||||
{'Content-Type': 'application/json',
|
||||
'Authorization': 'Basic %s' % string.strip(
|
||||
base64.encodestring('%s:%s'
|
||||
% (self.settings['user'],
|
||||
self.settings['passwd'])))
|
||||
}))
|
||||
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):
|
||||
h = {
|
||||
"Content-type": "application/x-www-form-urlencoded",
|
||||
"Content-type": "text/xml",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"X-Plex-Version": self.settings['version'],
|
||||
"X-Plex-Client-Identifier": self.settings['uuid'],
|
||||
|
@ -143,7 +175,7 @@ class jsonClass():
|
|||
self.logMsg("Empty response from XBMC", 1)
|
||||
return {}
|
||||
else:
|
||||
parsed=json.loads(jsonraw)
|
||||
parsed = json.loads(jsonraw)
|
||||
if parsed.get('error', False):
|
||||
self.logMsg("XBMC returned an error: %s" % parsed.get('error'), -1)
|
||||
return parsed.get('result', {})
|
||||
|
@ -156,6 +188,27 @@ class jsonClass():
|
|||
ret[player['type']] = player
|
||||
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):
|
||||
ret = []
|
||||
for player in self.getPlayers().values():
|
||||
|
@ -178,7 +231,10 @@ class jsonClass():
|
|||
return players.get(xbmc_photo(), {}).get('playerid', None)
|
||||
|
||||
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))
|
||||
mute = ("0", "1")[answ.get('muted', False)]
|
||||
return (vol, mute)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import re
|
||||
import traceback
|
||||
from SocketServer import ThreadingMixIn
|
||||
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
||||
from urlparse import urlparse, parse_qs
|
||||
|
@ -13,6 +12,7 @@ from utils import logging
|
|||
@logging
|
||||
class MyHandler(BaseHTTPRequestHandler):
|
||||
protocol_version = 'HTTP/1.1'
|
||||
regex = re.compile(r'''/playQueues/(\d+)$''')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
|
||||
|
@ -38,18 +38,20 @@ class MyHandler(BaseHTTPRequestHandler):
|
|||
def do_OPTIONS(self):
|
||||
self.send_response(200)
|
||||
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('Connection', 'close')
|
||||
self.send_header('Access-Control-Max-Age', '1209600')
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods',
|
||||
'POST, GET, OPTIONS, DELETE, PUT, HEAD')
|
||||
self.send_header('Access-Control-Allow-Headers',
|
||||
'x-plex-version, x-plex-platform-version, '
|
||||
'x-plex-username, x-plex-client-identifier, '
|
||||
'x-plex-target-client-identifier, x-plex-device-name, '
|
||||
'x-plex-platform, x-plex-product, accept, x-plex-device')
|
||||
'POST, GET, OPTIONS, DELETE, PUT, HEAD')
|
||||
self.send_header(
|
||||
'Access-Control-Allow-Headers',
|
||||
'x-plex-version, x-plex-platform-version, x-plex-username, '
|
||||
'x-plex-client-identifier, x-plex-target-client-identifier, '
|
||||
'x-plex-device-name, x-plex-platform, x-plex-product, accept, '
|
||||
'x-plex-device')
|
||||
self.end_headers()
|
||||
self.wfile.close()
|
||||
|
||||
|
@ -71,9 +73,10 @@ class MyHandler(BaseHTTPRequestHandler):
|
|||
|
||||
def answer_request(self, sendData):
|
||||
self.serverlist = self.server.client.getServerList()
|
||||
self.subMgr = self.server.subscriptionManager
|
||||
self.js = self.server.jsonClass
|
||||
self.settings = self.server.settings
|
||||
subMgr = self.server.subscriptionManager
|
||||
js = self.server.jsonClass
|
||||
settings = self.server.settings
|
||||
queue = self.server.queue
|
||||
|
||||
try:
|
||||
request_path = self.path[1:]
|
||||
|
@ -83,60 +86,82 @@ class MyHandler(BaseHTTPRequestHandler):
|
|||
params = {}
|
||||
for key in paramarrays:
|
||||
params[key] = paramarrays[key][0]
|
||||
self.logMsg("remote request_path: %s" % request_path, 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))
|
||||
if request_path=="version":
|
||||
self.response("PleXBMC Helper Remote Redirector: Running\r\nVersion: %s" % self.settings['version'])
|
||||
elif request_path=="verify":
|
||||
result=self.js.jsonrpc("ping")
|
||||
self.response("XBMC JSON connection test:\r\n"+result)
|
||||
subMgr.updateCommandID(self.headers.get(
|
||||
'X-Plex-Client-Identifier',
|
||||
self.client_address[0]),
|
||||
params.get('commandID', False))
|
||||
if request_path == "version":
|
||||
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:
|
||||
resp = getXMLHeader()
|
||||
resp += "<MediaContainer>"
|
||||
resp += "<Player"
|
||||
resp += ' title="%s"' % self.settings['client_name']
|
||||
resp += ' protocol="plex"'
|
||||
resp += ' protocolVersion="1"'
|
||||
resp += ' protocolCapabilities="navigation,playback,timeline"'
|
||||
resp += ' machineIdentifier="%s"' % self.settings['uuid']
|
||||
resp += ' product="PlexKodiConnect"'
|
||||
resp += ' platform="%s"' % self.settings['platform']
|
||||
resp += ' platformVersion="%s"' % self.settings['plexbmc_version']
|
||||
resp += ' deviceClass="pc"'
|
||||
resp += "/>"
|
||||
resp += "</MediaContainer>"
|
||||
resp = ('%s'
|
||||
'<MediaContainer>'
|
||||
'<Player'
|
||||
' title="%s"'
|
||||
' protocol="plex"'
|
||||
' protocolVersion="1"'
|
||||
' protocolCapabilities="navigation,playback,timeline"'
|
||||
' machineIdentifier="%s"'
|
||||
' product="PlexKodiConnect"'
|
||||
' platform="%s"'
|
||||
' platformVersion="%s"'
|
||||
' deviceClass="pc"'
|
||||
'/>'
|
||||
'</MediaContainer>'
|
||||
% (getXMLHeader(),
|
||||
settings['client_name'],
|
||||
settings['uuid'],
|
||||
settings['platform'],
|
||||
settings['plexbmc_version']))
|
||||
self.logMsg("crafted resources response: %s" % resp, 2)
|
||||
self.response(resp, self.js.getPlexHeaders())
|
||||
self.response(resp, js.getPlexHeaders())
|
||||
elif "/subscribe" in request_path:
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
protocol = params.get('protocol', False)
|
||||
host = self.client_address[0]
|
||||
port = params.get('port', False)
|
||||
uuid = self.headers.get('X-Plex-Client-Identifier', "")
|
||||
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:
|
||||
if params.get('wait', False) == '1':
|
||||
sleep(950)
|
||||
commandID = params.get('commandID', 0)
|
||||
self.response(re.sub(r"INSERTCOMMANDID", str(commandID), self.subMgr.msg(self.js.getPlayers())), {
|
||||
'X-Plex-Client-Identifier': self.settings['uuid'],
|
||||
'Access-Control-Expose-Headers': 'X-Plex-Client-Identifier',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'text/xml'
|
||||
})
|
||||
self.response(
|
||||
re.sub(r"INSERTCOMMANDID",
|
||||
str(commandID),
|
||||
subMgr.msg(js.getPlayers())),
|
||||
{
|
||||
'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:
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
uuid = self.headers.get('X-Plex-Client-Identifier', False) or self.client_address[0]
|
||||
self.subMgr.removeSubscriber(uuid)
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
uuid = self.headers.get('X-Plex-Client-Identifier', False) \
|
||||
or self.client_address[0]
|
||||
subMgr.removeSubscriber(uuid)
|
||||
elif request_path == "player/playback/setParameters":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
if 'volume' in params:
|
||||
volume = int(params['volume'])
|
||||
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:
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
offset = params.get('viewOffset', params.get('offset', "0"))
|
||||
protocol = params.get('protocol', "http")
|
||||
address = params.get('address', self.client_address[0])
|
||||
|
@ -146,78 +171,102 @@ class MyHandler(BaseHTTPRequestHandler):
|
|||
containerKey = urlparse(params.get('containerKey')).path
|
||||
except:
|
||||
containerKey = ''
|
||||
regex = re.compile(r'''/playQueues/(\d+)$''')
|
||||
try:
|
||||
playQueueID = regex.findall(containerKey)[0]
|
||||
playQueueID = self.regex.findall(containerKey)[0]
|
||||
except IndexError:
|
||||
playQueueID = ''
|
||||
|
||||
self.js.jsonrpc("playmedia", params)
|
||||
self.subMgr.lastkey = params['key']
|
||||
self.subMgr.containerKey = containerKey
|
||||
self.subMgr.playQueueID = playQueueID
|
||||
self.subMgr.server = server.get('server', 'localhost')
|
||||
self.subMgr.port = port
|
||||
self.subMgr.protocol = protocol
|
||||
self.subMgr.notify()
|
||||
# We need to tell service.py
|
||||
queue.put({
|
||||
'action': 'playlist',
|
||||
'data': params
|
||||
})
|
||||
subMgr.lastkey = params['key']
|
||||
subMgr.containerKey = containerKey
|
||||
subMgr.playQueueID = playQueueID
|
||||
subMgr.server = server.get('server', 'localhost')
|
||||
subMgr.port = port
|
||||
subMgr.protocol = protocol
|
||||
subMgr.notify()
|
||||
elif request_path == "player/playback/play":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
for playerid in self.js.getPlayerIds():
|
||||
self.js.jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": True})
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
for playerid in js.getPlayerIds():
|
||||
js.jsonrpc("Player.PlayPause",
|
||||
{"playerid": playerid, "play": True})
|
||||
subMgr.notify()
|
||||
elif request_path == "player/playback/pause":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
for playerid in self.js.getPlayerIds():
|
||||
self.js.jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": False})
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
for playerid in js.getPlayerIds():
|
||||
js.jsonrpc("Player.PlayPause",
|
||||
{"playerid": playerid, "play": False})
|
||||
subMgr.notify()
|
||||
elif request_path == "player/playback/stop":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
for playerid in self.js.getPlayerIds():
|
||||
self.js.jsonrpc("Player.Stop", {"playerid" : playerid})
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
for playerid in js.getPlayerIds():
|
||||
js.jsonrpc("Player.Stop", {"playerid": playerid})
|
||||
subMgr.notify()
|
||||
elif request_path == "player/playback/seekTo":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
for playerid in self.js.getPlayerIds():
|
||||
self.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":millisToTime(params.get('offset', 0))})
|
||||
self.subMgr.notify()
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
for playerid in js.getPlayerIds():
|
||||
js.jsonrpc("Player.Seek",
|
||||
{"playerid": playerid,
|
||||
"value": millisToTime(
|
||||
params.get('offset', 0))})
|
||||
subMgr.notify()
|
||||
elif request_path == "player/playback/stepForward":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
for playerid in self.js.getPlayerIds():
|
||||
self.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallforward"})
|
||||
self.subMgr.notify()
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
for playerid in js.getPlayerIds():
|
||||
js.jsonrpc("Player.Seek",
|
||||
{"playerid": playerid,
|
||||
"value": "smallforward"})
|
||||
subMgr.notify()
|
||||
elif request_path == "player/playback/stepBack":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
for playerid in self.js.getPlayerIds():
|
||||
self.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallbackward"})
|
||||
self.subMgr.notify()
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
for playerid in js.getPlayerIds():
|
||||
js.jsonrpc("Player.Seek",
|
||||
{"playerid": playerid,
|
||||
"value": "smallbackward"})
|
||||
subMgr.notify()
|
||||
elif request_path == "player/playback/skipNext":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
for playerid in self.js.getPlayerIds():
|
||||
self.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigforward"})
|
||||
self.subMgr.notify()
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
for playerid in js.getPlayerIds():
|
||||
js.jsonrpc("Player.GoTo",
|
||||
{"playerid": playerid,
|
||||
"to": "next"})
|
||||
subMgr.notify()
|
||||
elif request_path == "player/playback/skipPrevious":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
for playerid in self.js.getPlayerIds():
|
||||
self.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigbackward"})
|
||||
self.subMgr.notify()
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
for playerid in js.getPlayerIds():
|
||||
js.jsonrpc("Player.GoTo",
|
||||
{"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":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
self.js.jsonrpc("Input.Up")
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
js.jsonrpc("Input.Up")
|
||||
elif request_path == "player/navigation/moveDown":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
self.js.jsonrpc("Input.Down")
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
js.jsonrpc("Input.Down")
|
||||
elif request_path == "player/navigation/moveLeft":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
self.js.jsonrpc("Input.Left")
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
js.jsonrpc("Input.Left")
|
||||
elif request_path == "player/navigation/moveRight":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
self.js.jsonrpc("Input.Right")
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
js.jsonrpc("Input.Right")
|
||||
elif request_path == "player/navigation/select":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
self.js.jsonrpc("Input.Select")
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
js.jsonrpc("Input.Select")
|
||||
elif request_path == "player/navigation/home":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
self.js.jsonrpc("Input.Home")
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
js.jsonrpc("Input.Home")
|
||||
elif request_path == "player/navigation/back":
|
||||
self.response(getOKMsg(), self.js.getPlexHeaders())
|
||||
self.js.jsonrpc("Input.Back")
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
js.jsonrpc("Input.Back")
|
||||
else:
|
||||
self.logMsg('Unknown request path: %s' % request_path, -1)
|
||||
# elif 'player/mirror/details' in request_path:
|
||||
# # Detailed e.g. Movie information page was opened
|
||||
# # CURRENTLY NOT POSSIBLE DUE TO KODI RESTRICTIONS
|
||||
|
@ -240,6 +289,7 @@ class MyHandler(BaseHTTPRequestHandler):
|
|||
|
||||
except:
|
||||
self.logMsg('Error encountered. Traceback:', -1)
|
||||
import traceback
|
||||
self.logMsg(traceback.print_exc(), -1)
|
||||
|
||||
|
||||
|
@ -247,7 +297,7 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
|||
daemon_threads = True
|
||||
|
||||
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-
|
||||
date serverlist without instantiating anything
|
||||
|
@ -258,4 +308,5 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
|||
self.subscriptionManager = subscriptionManager
|
||||
self.jsonClass = jsonClass
|
||||
self.settings = settings
|
||||
self.queue = queue
|
||||
HTTPServer.__init__(self, *args, **kwargs)
|
||||
|
|
|
@ -22,18 +22,15 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|||
MA 02110-1301, USA.
|
||||
"""
|
||||
|
||||
__author__ = 'DHJ (hippojay) <plex@h-jay.com>'
|
||||
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
|
||||
from xbmc import sleep
|
||||
|
||||
import downloadutils
|
||||
from PlexFunctions import PMSHttpsEnabled
|
||||
from utils import window, logging
|
||||
from utils import window, logging, settings
|
||||
|
||||
|
||||
@logging
|
||||
|
@ -60,7 +57,7 @@ class plexgdm:
|
|||
self.client_registered = False
|
||||
self.download = downloadutils.DownloadUtils().downloadUrl
|
||||
|
||||
def clientDetails(self, settings):
|
||||
def clientDetails(self, options):
|
||||
self.client_data = (
|
||||
"Content-Type: plex/media-player\r\n"
|
||||
"Resource-Identifier: %s\r\n"
|
||||
|
@ -74,13 +71,13 @@ class plexgdm:
|
|||
"mirror,playqueues\r\n"
|
||||
"Device-Class: HTPC"
|
||||
) % (
|
||||
settings['uuid'],
|
||||
settings['client_name'],
|
||||
settings['myport'],
|
||||
settings['addonName'],
|
||||
settings['version']
|
||||
options['uuid'],
|
||||
options['client_name'],
|
||||
options['myport'],
|
||||
options['addonName'],
|
||||
options['version']
|
||||
)
|
||||
self.client_id = settings['uuid']
|
||||
self.client_id = options['uuid']
|
||||
|
||||
def getClientDetails(self):
|
||||
if not self.client_data:
|
||||
|
@ -215,119 +212,28 @@ class plexgdm:
|
|||
return self.server_list
|
||||
|
||||
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')
|
||||
if currServer:
|
||||
currServerProt, currServerIP, currServerPort = \
|
||||
currServer.split(':')
|
||||
currServerIP = currServerIP.replace('/', '')
|
||||
for server in discovered_servers:
|
||||
if server['server'] == currServerIP:
|
||||
break
|
||||
else:
|
||||
# Currently active server was not discovered via GDM; ADD
|
||||
update = {
|
||||
'port': currServerPort,
|
||||
'protocol': currServerProt,
|
||||
'class': None,
|
||||
'content-type': 'plex/media-server',
|
||||
'discovery': 'auto',
|
||||
'master': 1,
|
||||
'owned': '1',
|
||||
'role': 'master',
|
||||
'server': currServerIP,
|
||||
'serverName': window('plex_servername'),
|
||||
'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)
|
||||
if not currServer:
|
||||
return
|
||||
currServerProt, currServerIP, currServerPort = \
|
||||
currServer.split(':')
|
||||
currServerIP = currServerIP.replace('/', '')
|
||||
# Currently active server was not discovered via GDM; ADD
|
||||
self.server_list = [{
|
||||
'port': currServerPort,
|
||||
'protocol': currServerProt,
|
||||
'class': None,
|
||||
'content-type': 'plex/media-server',
|
||||
'discovery': 'auto',
|
||||
'master': 1,
|
||||
'owned': '1',
|
||||
'role': 'master',
|
||||
'server': currServerIP,
|
||||
'serverName': window('plex_servername'),
|
||||
'updated': int(time.time()),
|
||||
'uuid': window('plex_machineIdentifier'),
|
||||
'version': 'irrelevant'
|
||||
}]
|
||||
|
||||
def setInterval(self, interval):
|
||||
self.discovery_interval = interval
|
||||
|
@ -388,4 +294,5 @@ class plexgdm:
|
|||
|
||||
def start_all(self, daemon=False):
|
||||
self.start_discovery(daemon)
|
||||
self.start_registration(daemon)
|
||||
if settings('plexCompanion') == 'true':
|
||||
self.start_registration(daemon)
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import re
|
||||
import threading
|
||||
|
||||
from xbmc import Player
|
||||
|
||||
import downloadutils
|
||||
from utils import window, logging
|
||||
import PlexFunctions as pf
|
||||
|
@ -11,13 +9,19 @@ from functions import *
|
|||
|
||||
@logging
|
||||
class SubscriptionManager:
|
||||
def __init__(self, jsonClass, RequestMgr):
|
||||
def __init__(self, jsonClass, RequestMgr, player, playlist):
|
||||
self.serverlist = []
|
||||
self.subscribers = {}
|
||||
self.info = {}
|
||||
self.lastkey = ""
|
||||
self.containerKey = ""
|
||||
self.lastratingkey = ""
|
||||
self.ratingkey = ""
|
||||
self.lastplayers = {}
|
||||
self.lastinfo = {
|
||||
'video': {},
|
||||
'audio': {},
|
||||
'picture': {}
|
||||
}
|
||||
self.volume = 0
|
||||
self.mute = '0'
|
||||
self.server = ""
|
||||
|
@ -25,7 +29,8 @@ class SubscriptionManager:
|
|||
self.port = ""
|
||||
self.playerprops = {}
|
||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||
self.xbmcplayer = Player()
|
||||
self.xbmcplayer = player
|
||||
self.playlist = playlist
|
||||
|
||||
self.js = jsonClass
|
||||
self.RequestMgr = RequestMgr
|
||||
|
@ -62,7 +67,7 @@ class SubscriptionManager:
|
|||
msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
|
||||
msg += "\r\n</MediaContainer>"
|
||||
return msg
|
||||
|
||||
|
||||
def getTimelineXML(self, playerid, ptype):
|
||||
if playerid is not None:
|
||||
info = self.getPlayerProperties(playerid)
|
||||
|
@ -95,21 +100,22 @@ class SubscriptionManager:
|
|||
xbmc.sleep(100)
|
||||
count += 1
|
||||
if keyid:
|
||||
self.lastkey = "/library/metadata/%s"%keyid
|
||||
self.lastratingkey = keyid
|
||||
ret += ' location="%s"' % (self.mainlocation)
|
||||
ret += ' key="%s"' % (self.lastkey)
|
||||
ret += ' ratingKey="%s"' % (self.lastratingkey)
|
||||
self.lastkey = "/library/metadata/%s" % keyid
|
||||
self.ratingkey = keyid
|
||||
ret += ' location="%s"' % self.mainlocation
|
||||
ret += ' key="%s"' % self.lastkey
|
||||
ret += ' ratingKey="%s"' % self.ratingkey
|
||||
serv = self.getServerByHost(self.server)
|
||||
if info.get('playQueueID'):
|
||||
self.containerKey = "/playQueues/%s" % info.get('playQueueID')
|
||||
ret += ' playQueueID="%s"' % info.get('playQueueID')
|
||||
ret += ' playQueueVersion="%s"' % info.get('playQueueVersion')
|
||||
ret += ' playQueueItemID="%s"' % (info.get('playQueueItemID'))
|
||||
ret += ' playQueueItemID="%s"' % info.get('playQueueItemID')
|
||||
ret += ' containerKey="%s"' % self.containerKey
|
||||
ret += ' guid="%s"' % info['guid']
|
||||
elif keyid:
|
||||
self.containerKey = self.lastkey
|
||||
ret += ' containerKey="%s"' % (self.containerKey)
|
||||
ret += ' containerKey="%s"' % self.containerKey
|
||||
|
||||
ret += ' duration="%s"' % info['duration']
|
||||
ret += ' seekRange="0-%s"' % info['duration']
|
||||
|
@ -118,7 +124,6 @@ class SubscriptionManager:
|
|||
ret += ' protocol="%s"' % serv.get('protocol', "http")
|
||||
ret += ' address="%s"' % serv.get('server', self.server)
|
||||
ret += ' port="%s"' % serv.get('port', self.port)
|
||||
ret += ' guid="%s"' % info['guid']
|
||||
ret += ' volume="%s"' % info['volume']
|
||||
ret += ' shuffle="%s"' % info['shuffle']
|
||||
ret += ' mute="%s"' % self.mute
|
||||
|
@ -132,13 +137,14 @@ class SubscriptionManager:
|
|||
|
||||
def updateCommandID(self, uuid, commandID):
|
||||
if commandID and self.subscribers.get(uuid, False):
|
||||
self.subscribers[uuid].commandID = int(commandID)
|
||||
|
||||
def notify(self, event = False):
|
||||
self.subscribers[uuid].commandID = int(commandID)
|
||||
|
||||
def notify(self, event=False):
|
||||
self.cleanup()
|
||||
# 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
|
||||
if not window('Plex_currently_playing_itemid'):
|
||||
if (not window('Plex_currently_playing_itemid')
|
||||
and not self.lastplayers):
|
||||
return True
|
||||
players = self.js.getPlayers()
|
||||
# fetch the message, subscribers or not, since the server
|
||||
|
@ -147,35 +153,46 @@ class SubscriptionManager:
|
|||
if self.subscribers:
|
||||
with threading.RLock():
|
||||
for sub in self.subscribers.values():
|
||||
sub.send_update(msg, len(players)==0)
|
||||
sub.send_update(msg, len(players) == 0)
|
||||
self.notifyServer(players)
|
||||
self.lastplayers = players
|
||||
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)
|
||||
url = serv.get('protocol', 'http') + '://' \
|
||||
+ serv.get('server', 'localhost') + ':' \
|
||||
+ serv.get('port', '32400') + "/:/timeline"
|
||||
self.doUtils(url, type="GET", parameters=params)
|
||||
# requests.getwithparams(serv.get('server', 'localhost'), serv.get('port', 32400), "/:/timeline", params, getPlexHeaders(), serv.get('protocol', 'http'))
|
||||
self.logMsg("sent server notification with state = %s"
|
||||
% params['state'], 2)
|
||||
def notifyServer(self, players):
|
||||
for typus, p in players.iteritems():
|
||||
info = self.playerprops[p.get('playerid')]
|
||||
self._sendNotification(info)
|
||||
self.lastinfo[typus] = info
|
||||
# Cross the one of the list
|
||||
try:
|
||||
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):
|
||||
return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause"
|
||||
|
@ -205,30 +222,50 @@ class SubscriptionManager:
|
|||
if sub.age > 30:
|
||||
sub.cleanup()
|
||||
del self.subscribers[sub.uuid]
|
||||
|
||||
|
||||
def getPlayerProperties(self, playerid):
|
||||
info = {}
|
||||
try:
|
||||
# get info from the player
|
||||
props = self.js.jsonrpc("Player.GetProperties", {"playerid": playerid, "properties": ["time", "totaltime", "speed", "shuffled", "repeat"]})
|
||||
self.logMsg(self.js.jsonrpc("Player.GetItem", {"playerid": playerid, "properties": ["file", "showlink", "episode", "season"]}), 2)
|
||||
info['time'] = timeToMillis(props['time'])
|
||||
info['duration'] = timeToMillis(props['totaltime'])
|
||||
info['state'] = ("paused", "playing")[int(props['speed'])]
|
||||
info['shuffle'] = ("0","1")[props.get('shuffled', False)]
|
||||
info['repeat'] = pf.getPlexRepeat(props.get('repeat'))
|
||||
# New PMS playQueue attributes
|
||||
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)
|
||||
props = self.js.jsonrpc(
|
||||
"Player.GetProperties",
|
||||
{"playerid": playerid,
|
||||
"properties": ["time",
|
||||
"totaltime",
|
||||
"speed",
|
||||
"shuffled",
|
||||
"repeat"]})
|
||||
|
||||
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:
|
||||
info['time'] = 0
|
||||
info['duration'] = 0
|
||||
info['state'] = "stopped"
|
||||
info['shuffle'] = False
|
||||
import traceback
|
||||
self.logMsg("Traceback:\n%s"
|
||||
% traceback.format_exc(), -1)
|
||||
info = {
|
||||
'time': 0,
|
||||
'duration': 0,
|
||||
'state': 'stopped',
|
||||
'shuffle': False,
|
||||
'repeat': 0
|
||||
}
|
||||
|
||||
# get the volume from the application
|
||||
info['volume'] = self.volume
|
||||
info['mute'] = self.mute
|
||||
|
@ -283,6 +320,6 @@ class Subscriber:
|
|||
"""
|
||||
response = self.doUtils(url,
|
||||
postBody=msg,
|
||||
type="POST")
|
||||
action_type="POST")
|
||||
if response in [False, None, 401]:
|
||||
self.subMgr.removeSubscriber(self.uuid)
|
||||
|
|
|
@ -31,8 +31,7 @@ class Read_EmbyServer():
|
|||
# This will return the full item
|
||||
item = {}
|
||||
|
||||
url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
|
||||
result = self.doUtils(url)
|
||||
result = self.doUtils("{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid)
|
||||
if result:
|
||||
item = result
|
||||
|
||||
|
@ -45,13 +44,12 @@ class Read_EmbyServer():
|
|||
itemlists = self.split_list(itemlist, 50)
|
||||
for itemlist in itemlists:
|
||||
# Will return basic information
|
||||
url = "{server}/emby/Users/{UserId}/Items?&format=json"
|
||||
params = {
|
||||
|
||||
'Ids': ",".join(itemlist),
|
||||
'Fields': "Etag"
|
||||
}
|
||||
result = self.doUtils(url, parameters=params)
|
||||
result = self.doUtils("{server}/emby/Users/{UserId}/Items?&format=json", parameters=params)
|
||||
if result:
|
||||
items.extend(result['Items'])
|
||||
|
||||
|
@ -64,7 +62,6 @@ class Read_EmbyServer():
|
|||
itemlists = self.split_list(itemlist, 50)
|
||||
for itemlist in itemlists:
|
||||
|
||||
url = "{server}/emby/Users/{UserId}/Items?format=json"
|
||||
params = {
|
||||
|
||||
"Ids": ",".join(itemlist),
|
||||
|
@ -75,25 +72,22 @@ class Read_EmbyServer():
|
|||
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
||||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||
"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:
|
||||
items.extend(result['Items'])
|
||||
|
||||
return items
|
||||
|
||||
def getView_embyId(self, itemid):
|
||||
# Returns ancestors using embyId
|
||||
def getView_plexid(self, itemid):
|
||||
# Returns ancestors using plexid
|
||||
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 viewtype == "CollectionFolder":
|
||||
if view['Type'] == "CollectionFolder":
|
||||
# Found view
|
||||
viewId = view['Id']
|
||||
|
||||
|
@ -120,8 +114,6 @@ class Read_EmbyServer():
|
|||
return [viewName, viewId, mediatype]
|
||||
|
||||
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 = {
|
||||
|
||||
'ParentId': parentid,
|
||||
|
@ -140,11 +132,9 @@ class Read_EmbyServer():
|
|||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||
"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):
|
||||
doUtils = self.doUtils
|
||||
url = "{server}/emby/LiveTv/Channels/?userid={UserId}&format=json"
|
||||
params = {
|
||||
|
||||
'EnableImages': True,
|
||||
|
@ -154,11 +144,9 @@ class Read_EmbyServer():
|
|||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||
"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):
|
||||
doUtils = self.doUtils
|
||||
url = "{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json"
|
||||
if groupid == "root": groupid = ""
|
||||
params = {
|
||||
|
||||
|
@ -170,13 +158,10 @@ class Read_EmbyServer():
|
|||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||
"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):
|
||||
|
||||
log = self.logMsg
|
||||
|
||||
doUtils = self.doUtils
|
||||
items = {
|
||||
|
||||
'Items': [],
|
||||
|
@ -195,13 +180,13 @@ class Read_EmbyServer():
|
|||
'Recursive': True,
|
||||
'Limit': 1
|
||||
}
|
||||
result = doUtils(url, parameters=params)
|
||||
result = self.doUtils(url, parameters=params)
|
||||
try:
|
||||
total = result['TotalRecordCount']
|
||||
items['TotalRecordCount'] = total
|
||||
|
||||
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:
|
||||
index = 0
|
||||
|
@ -234,36 +219,36 @@ class Read_EmbyServer():
|
|||
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
||||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,"
|
||||
"MediaSources"
|
||||
"MediaSources,VoteCount"
|
||||
)
|
||||
result = doUtils(url, parameters=params)
|
||||
result = self.doUtils(url, parameters=params)
|
||||
try:
|
||||
items['Items'].extend(result['Items'])
|
||||
except TypeError:
|
||||
# Something happened to the connection
|
||||
if not throttled:
|
||||
throttled = True
|
||||
log("Throttle activated.", 1)
|
||||
self.logMsg("Throttle activated.", 1)
|
||||
|
||||
if jump == highestjump:
|
||||
# We already tried with the highestjump, but it failed. Reset value.
|
||||
log("Reset highest value.", 1)
|
||||
self.logMsg("Reset highest value.", 1)
|
||||
highestjump = 0
|
||||
|
||||
# Lower the number by half
|
||||
if highestjump:
|
||||
throttled = False
|
||||
jump = highestjump
|
||||
log("Throttle deactivated.", 1)
|
||||
self.logMsg("Throttle deactivated.", 1)
|
||||
else:
|
||||
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
|
||||
while utils.window('emby_online') != "true":
|
||||
while utils.window('plex_online') != "true":
|
||||
# Wait server to come back online
|
||||
if retry == 5:
|
||||
log("Unable to reconnect to server. Abort process.", 1)
|
||||
self.logMsg("Unable to reconnect to server. Abort process.", 1)
|
||||
return items
|
||||
|
||||
retry += 1
|
||||
|
@ -291,12 +276,11 @@ class Read_EmbyServer():
|
|||
increment = 10
|
||||
|
||||
jump += increment
|
||||
log("Increase jump limit to: %s" % jump, 1)
|
||||
self.logMsg("Increase jump limit to: %s" % jump, 1)
|
||||
return items
|
||||
|
||||
def getViews(self, mediatype="", root=False, sortedlist=False):
|
||||
# Build a list of user views
|
||||
doUtils = self.doUtils
|
||||
views = []
|
||||
mediatype = mediatype.lower()
|
||||
|
||||
|
@ -305,7 +289,7 @@ class Read_EmbyServer():
|
|||
else: # Views ungrouped
|
||||
url = "{server}/emby/Users/{UserId}/Items?Sortby=SortName&format=json"
|
||||
|
||||
result = doUtils(url)
|
||||
result = self.doUtils(url)
|
||||
try:
|
||||
items = result['Items']
|
||||
except TypeError:
|
||||
|
@ -313,11 +297,8 @@ class Read_EmbyServer():
|
|||
else:
|
||||
for item in items:
|
||||
|
||||
name = item['Name']
|
||||
itemId = item['Id']
|
||||
viewtype = item['Type']
|
||||
|
||||
if viewtype == "Channel":
|
||||
item['Name'] = item['Name']
|
||||
if item['Type'] == "Channel":
|
||||
# Filter view types
|
||||
continue
|
||||
|
||||
|
@ -328,20 +309,20 @@ class Read_EmbyServer():
|
|||
# Assumed missing is mixed then.
|
||||
'''if itemtype is None:
|
||||
url = "{server}/emby/Library/MediaFolders?format=json"
|
||||
result = doUtils(url)
|
||||
result = self.doUtils(url)
|
||||
|
||||
for folder in result['Items']:
|
||||
if itemId == folder['Id']:
|
||||
if item['Id'] == folder['Id']:
|
||||
itemtype = folder.get('CollectionType', "mixed")'''
|
||||
|
||||
if name not in ('Collections', 'Trailers'):
|
||||
if item['Name'] not in ('Collections', 'Trailers'):
|
||||
|
||||
if sortedlist:
|
||||
views.append({
|
||||
|
||||
'name': name,
|
||||
'name': item['Name'],
|
||||
'type': itemtype,
|
||||
'id': itemId
|
||||
'id': item['Id']
|
||||
})
|
||||
|
||||
elif (itemtype == mediatype or
|
||||
|
@ -349,9 +330,9 @@ class Read_EmbyServer():
|
|||
|
||||
views.append({
|
||||
|
||||
'name': name,
|
||||
'name': item['Name'],
|
||||
'type': itemtype,
|
||||
'id': itemId
|
||||
'id': item['Id']
|
||||
})
|
||||
|
||||
return views
|
||||
|
@ -359,8 +340,6 @@ class Read_EmbyServer():
|
|||
def verifyView(self, parentid, itemid):
|
||||
|
||||
belongs = False
|
||||
|
||||
url = "{server}/emby/Users/{UserId}/Items?format=json"
|
||||
params = {
|
||||
|
||||
'ParentId': parentid,
|
||||
|
@ -370,7 +349,7 @@ class Read_EmbyServer():
|
|||
'Recursive': True,
|
||||
'Ids': itemid
|
||||
}
|
||||
result = self.doUtils(url, parameters=params)
|
||||
result = self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
|
||||
try:
|
||||
total = result['TotalRecordCount']
|
||||
except TypeError:
|
||||
|
@ -383,40 +362,23 @@ class Read_EmbyServer():
|
|||
return belongs
|
||||
|
||||
def getMovies(self, parentId, basic=False, dialog=None):
|
||||
|
||||
items = self.getSection(parentId, "Movie", basic=basic, dialog=dialog)
|
||||
|
||||
return items
|
||||
return self.getSection(parentId, "Movie", basic=basic, dialog=dialog)
|
||||
|
||||
def getBoxset(self, dialog=None):
|
||||
|
||||
items = self.getSection(None, "BoxSet", dialog=dialog)
|
||||
|
||||
return items
|
||||
return self.getSection(None, "BoxSet", dialog=dialog)
|
||||
|
||||
def getMovies_byBoxset(self, boxsetid):
|
||||
|
||||
items = self.getSection(boxsetid, "Movie")
|
||||
|
||||
return items
|
||||
return self.getSection(boxsetid, "Movie")
|
||||
|
||||
def getMusicVideos(self, parentId, basic=False, dialog=None):
|
||||
|
||||
items = self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog)
|
||||
|
||||
return items
|
||||
return self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog)
|
||||
|
||||
def getHomeVideos(self, parentId):
|
||||
|
||||
items = self.getSection(parentId, "Video")
|
||||
|
||||
return items
|
||||
return self.getSection(parentId, "Video")
|
||||
|
||||
def getShows(self, parentId, basic=False, dialog=None):
|
||||
|
||||
items = self.getSection(parentId, "Series", basic=basic, dialog=dialog)
|
||||
|
||||
return items
|
||||
return self.getSection(parentId, "Series", basic=basic, dialog=dialog)
|
||||
|
||||
def getSeasons(self, showId):
|
||||
|
||||
|
@ -426,13 +388,12 @@ class Read_EmbyServer():
|
|||
'TotalRecordCount': 0
|
||||
}
|
||||
|
||||
url = "{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId
|
||||
params = {
|
||||
|
||||
'IsVirtualUnaired': False,
|
||||
'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:
|
||||
items = result
|
||||
|
||||
|
@ -440,25 +401,19 @@ class Read_EmbyServer():
|
|||
|
||||
def getEpisodes(self, parentId, basic=False, dialog=None):
|
||||
|
||||
items = self.getSection(parentId, "Episode", basic=basic, dialog=dialog)
|
||||
|
||||
return items
|
||||
return self.getSection(parentId, "Episode", basic=basic, dialog=dialog)
|
||||
|
||||
def getEpisodesbyShow(self, showId):
|
||||
|
||||
items = self.getSection(showId, "Episode")
|
||||
|
||||
return items
|
||||
return self.getSection(showId, "Episode")
|
||||
|
||||
def getEpisodesbySeason(self, seasonId):
|
||||
|
||||
items = self.getSection(seasonId, "Episode")
|
||||
return self.getSection(seasonId, "Episode")
|
||||
|
||||
return items
|
||||
|
||||
def getArtists(self, dialog=None):
|
||||
|
||||
doUtils = self.doUtils
|
||||
items = {
|
||||
|
||||
'Items': [],
|
||||
|
@ -472,7 +427,7 @@ class Read_EmbyServer():
|
|||
'Recursive': True,
|
||||
'Limit': 1
|
||||
}
|
||||
result = doUtils(url, parameters=params)
|
||||
result = self.doUtils(url, parameters=params)
|
||||
try:
|
||||
total = result['TotalRecordCount']
|
||||
items['TotalRecordCount'] = total
|
||||
|
@ -502,7 +457,7 @@ class Read_EmbyServer():
|
|||
"AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview"
|
||||
)
|
||||
}
|
||||
result = doUtils(url, parameters=params)
|
||||
result = self.doUtils(url, parameters=params)
|
||||
items['Items'].extend(result['Items'])
|
||||
|
||||
index += jump
|
||||
|
@ -512,28 +467,17 @@ class Read_EmbyServer():
|
|||
return items
|
||||
|
||||
def getAlbums(self, basic=False, dialog=None):
|
||||
|
||||
items = self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog)
|
||||
|
||||
return items
|
||||
return self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog)
|
||||
|
||||
def getAlbumsbyArtist(self, artistId):
|
||||
|
||||
items = self.getSection(artistId, "MusicAlbum", sortby="DateCreated")
|
||||
|
||||
return items
|
||||
return self.getSection(artistId, "MusicAlbum", sortby="DateCreated")
|
||||
|
||||
def getSongs(self, basic=False, dialog=None):
|
||||
|
||||
items = self.getSection(None, "Audio", basic=basic, dialog=dialog)
|
||||
|
||||
return items
|
||||
return self.getSection(None, "Audio", basic=basic, dialog=dialog)
|
||||
|
||||
def getSongsbyAlbum(self, albumId):
|
||||
return self.getSection(albumId, "Audio")
|
||||
|
||||
items = self.getSection(albumId, "Audio")
|
||||
|
||||
return items
|
||||
|
||||
def getAdditionalParts(self, itemId):
|
||||
|
||||
|
@ -543,8 +487,7 @@ class Read_EmbyServer():
|
|||
'TotalRecordCount': 0
|
||||
}
|
||||
|
||||
url = "{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId
|
||||
result = self.doUtils(url)
|
||||
result = self.doUtils("{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId)
|
||||
if result:
|
||||
items = result
|
||||
|
||||
|
@ -566,25 +509,21 @@ class Read_EmbyServer():
|
|||
|
||||
def updateUserRating(self, itemid, like=None, favourite=None, deletelike=False):
|
||||
# Updates the user rating to Emby
|
||||
doUtils = self.doUtils
|
||||
|
||||
if favourite:
|
||||
url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid
|
||||
doUtils(url, type="POST")
|
||||
self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="POST")
|
||||
elif favourite == False:
|
||||
url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid
|
||||
doUtils(url, type="DELETE")
|
||||
self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="DELETE")
|
||||
|
||||
if not deletelike and like:
|
||||
url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=true&format=json" % itemid
|
||||
doUtils(url, type="POST")
|
||||
elif not deletelike and like == False:
|
||||
url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid
|
||||
doUtil(url, type="POST")
|
||||
self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=true&format=json" % itemid, action_type="POST")
|
||||
elif not deletelike and like is False:
|
||||
self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid, action_type="POST")
|
||||
elif deletelike:
|
||||
url = "{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid
|
||||
doUtils(url, type="DELETE")
|
||||
self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid, action_type="DELETE")
|
||||
else:
|
||||
self.logMsg("Error processing user rating.", 1)
|
||||
|
||||
self.logMsg("Update user rating to emby for itemid: %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"
|
||||
# result = doUtils.downloadUrl(url)
|
||||
|
||||
# utils.settings('markPlayed', value=str(result['MaxResumePct']))
|
||||
|
||||
def hasAccess(self):
|
||||
# Plex: always return True for now
|
||||
return True
|
||||
|
@ -126,19 +124,19 @@ class UserClient(threading.Thread):
|
|||
url = "{server}/emby/Users?format=json"
|
||||
result = self.doUtils.downloadUrl(url)
|
||||
|
||||
if result == False:
|
||||
if result is False:
|
||||
# Access is restricted, set in downloadutils.py via exception
|
||||
log("Access is restricted.", 1)
|
||||
self.HasAccess = False
|
||||
|
||||
elif window('emby_online') != "true":
|
||||
elif window('plex_online') != "true":
|
||||
# Server connection failed
|
||||
pass
|
||||
|
||||
elif window('emby_serverStatus') == "restricted":
|
||||
elif window('plex_serverStatus') == "restricted":
|
||||
log("Access is granted.", 1)
|
||||
self.HasAccess = True
|
||||
window('emby_serverStatus', clear=True)
|
||||
window('plex_serverStatus', clear=True)
|
||||
xbmcgui.Dialog().notification(self.addonName,
|
||||
utils.language(33007))
|
||||
|
||||
|
@ -239,7 +237,7 @@ class UserClient(threading.Thread):
|
|||
# Give attempts at entering password / selecting user
|
||||
if self.retry >= 2:
|
||||
log("Too many retries to login.", -1)
|
||||
window('emby_serverStatus', value="Stop")
|
||||
window('plex_serverStatus', value="Stop")
|
||||
dialog.ok(lang(33001),
|
||||
lang(39023))
|
||||
xbmc.executebuiltin(
|
||||
|
@ -247,8 +245,8 @@ class UserClient(threading.Thread):
|
|||
return False
|
||||
|
||||
# Get /profile/addon_data
|
||||
addondir = xbmc.translatePath(
|
||||
self.addon.getAddonInfo('profile')).decode('utf-8')
|
||||
addondir = utils.tryDecode(xbmc.translatePath(
|
||||
self.addon.getAddonInfo('profile')))
|
||||
hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
|
||||
|
||||
# If there's no settings.xml
|
||||
|
@ -358,7 +356,7 @@ class UserClient(threading.Thread):
|
|||
break
|
||||
xbmc.sleep(1000)
|
||||
|
||||
status = window('emby_serverStatus')
|
||||
status = window('plex_serverStatus')
|
||||
|
||||
if status == "Stop":
|
||||
xbmc.sleep(500)
|
||||
|
@ -371,7 +369,7 @@ class UserClient(threading.Thread):
|
|||
|
||||
elif status == "401":
|
||||
# Unauthorized access, revoke token
|
||||
window('emby_serverStatus', value="Auth")
|
||||
window('plex_serverStatus', value="Auth")
|
||||
self.resetClient()
|
||||
xbmc.sleep(2000)
|
||||
|
||||
|
@ -389,7 +387,7 @@ class UserClient(threading.Thread):
|
|||
log("Current accessToken: xxxx", 1)
|
||||
self.retry = 0
|
||||
window('suspend_LibraryThread', clear=True)
|
||||
window('emby_serverStatus', clear=True)
|
||||
window('plex_serverStatus', clear=True)
|
||||
|
||||
if not self.auth and (self.currUser is None):
|
||||
# Loop if no server found
|
||||
|
|
|
@ -16,6 +16,7 @@ from functools import wraps
|
|||
from calendar import timegm
|
||||
import os
|
||||
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
@ -98,7 +99,7 @@ def IfExists(path):
|
|||
|
||||
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:
|
||||
etree.ElementTree(etree.Element('test')).write(dummyfile)
|
||||
except:
|
||||
|
@ -111,6 +112,46 @@ def IfExists(path):
|
|||
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):
|
||||
"""
|
||||
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):
|
||||
# Get the logLevel set in UserClient
|
||||
try:
|
||||
logLevel = int(window('emby_logLevel'))
|
||||
logLevel = int(window('plex_logLevel'))
|
||||
except ValueError:
|
||||
logLevel = 0
|
||||
kodiLevel = {
|
||||
|
@ -272,7 +313,7 @@ def logMsg(title, msg, level=1):
|
|||
except UnicodeEncodeError:
|
||||
try:
|
||||
xbmc.log("%s -> %s : %s" % (
|
||||
title, func.co_name, msg.encode('utf-8')),
|
||||
title, func.co_name, tryEncode(msg)),
|
||||
level=kodiLevel[level])
|
||||
except:
|
||||
xbmc.log("%s -> %s : %s" % (
|
||||
|
@ -283,7 +324,7 @@ def logMsg(title, msg, level=1):
|
|||
xbmc.log("%s -> %s" % (title, msg), level=kodiLevel[level])
|
||||
except UnicodeEncodeError:
|
||||
try:
|
||||
xbmc.log("%s -> %s" % (title, msg.encode('utf-8')),
|
||||
xbmc.log("%s -> %s" % (title, tryEncode(msg)),
|
||||
level=kodiLevel[level])
|
||||
except:
|
||||
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
|
||||
"""
|
||||
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
|
||||
'''if isinstance(property, unicode):
|
||||
property = property.encode("utf-8")
|
||||
if isinstance(value, unicode):
|
||||
value = value.encode("utf-8")'''
|
||||
if clear:
|
||||
WINDOW.clearProperty(property)
|
||||
elif value is not None:
|
||||
WINDOW.setProperty(property, value.encode('utf-8'))
|
||||
WINDOW.setProperty(property, tryEncode(value))
|
||||
else:
|
||||
return WINDOW.getProperty(property).decode('utf-8')
|
||||
return tryDecode(WINDOW.getProperty(property))
|
||||
|
||||
def settings(setting, value=None):
|
||||
"""
|
||||
|
@ -323,10 +360,10 @@ def settings(setting, value=None):
|
|||
|
||||
if value is not None:
|
||||
# Takes string or unicode by default!
|
||||
addon.setSetting(setting, value.encode('utf-8'))
|
||||
addon.setSetting(setting, tryEncode(value))
|
||||
else:
|
||||
# Should return unicode by default, but just in case
|
||||
return addon.getSetting(setting).decode('utf-8')
|
||||
return tryDecode(addon.getSetting(setting))
|
||||
|
||||
def language(stringid):
|
||||
# Central string retrieval
|
||||
|
@ -334,40 +371,39 @@ def language(stringid):
|
|||
string = addon.getLocalizedString(stringid) #returns unicode object
|
||||
return string
|
||||
|
||||
def kodiSQL(type="video"):
|
||||
|
||||
if type == "emby":
|
||||
dbPath = xbmc.translatePath("special://database/emby.db").decode('utf-8')
|
||||
elif type == "music":
|
||||
def kodiSQL(media_type="video"):
|
||||
|
||||
if media_type == "emby":
|
||||
dbPath = tryDecode(xbmc.translatePath("special://database/emby.db"))
|
||||
elif media_type == "music":
|
||||
dbPath = getKodiMusicDBPath()
|
||||
elif type == "texture":
|
||||
dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8')
|
||||
elif media_type == "texture":
|
||||
dbPath = tryDecode(xbmc.translatePath(
|
||||
"special://database/Textures13.db"))
|
||||
else:
|
||||
dbPath = getKodiVideoDBPath()
|
||||
|
||||
|
||||
connection = sqlite3.connect(dbPath)
|
||||
return connection
|
||||
|
||||
def getKodiVideoDBPath():
|
||||
|
||||
kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2]
|
||||
dbVersion = {
|
||||
|
||||
"13": 78, # Gotham
|
||||
"14": 90, # Helix
|
||||
"15": 93, # Isengard
|
||||
"16": 99, # Jarvis
|
||||
"17":104 # Krypton
|
||||
"17": 107 # Krypton
|
||||
}
|
||||
|
||||
dbPath = xbmc.translatePath(
|
||||
"special://database/MyVideos%s.db"
|
||||
% dbVersion.get(kodibuild, "")).decode('utf-8')
|
||||
dbPath = tryDecode(xbmc.translatePath(
|
||||
"special://database/MyVideos%s.db"
|
||||
% dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")))
|
||||
return dbPath
|
||||
|
||||
def getKodiMusicDBPath():
|
||||
|
||||
kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2]
|
||||
dbVersion = {
|
||||
|
||||
"13": 46, # Gotham
|
||||
|
@ -377,9 +413,9 @@ def getKodiMusicDBPath():
|
|||
"17": 60 # Krypton
|
||||
}
|
||||
|
||||
dbPath = xbmc.translatePath(
|
||||
"special://database/MyMusic%s.db"
|
||||
% dbVersion.get(kodibuild, "")).decode('utf-8')
|
||||
dbPath = tryDecode(xbmc.translatePath(
|
||||
"special://database/MyMusic%s.db"
|
||||
% dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")))
|
||||
return dbPath
|
||||
|
||||
def getScreensaver():
|
||||
|
@ -394,11 +430,7 @@ def getScreensaver():
|
|||
'setting': "screensaver.mode"
|
||||
}
|
||||
}
|
||||
result = xbmc.executeJSONRPC(json.dumps(query))
|
||||
result = json.loads(result)
|
||||
screensaver = result['result']['value']
|
||||
|
||||
return screensaver
|
||||
return json.loads(xbmc.executeJSONRPC(json.dumps(query)))['result']['value']
|
||||
|
||||
def setScreensaver(value):
|
||||
# Toggle the screensaver
|
||||
|
@ -413,21 +445,19 @@ def setScreensaver(value):
|
|||
'value': value
|
||||
}
|
||||
}
|
||||
result = xbmc.executeJSONRPC(json.dumps(query))
|
||||
logMsg("PLEX", "Toggling screensaver: %s %s" % (value, result), 1)
|
||||
logMsg("PLEX", "Toggling screensaver: %s %s" % (value, xbmc.executeJSONRPC(json.dumps(query))), 1)
|
||||
|
||||
def reset():
|
||||
|
||||
dialog = xbmcgui.Dialog()
|
||||
|
||||
resp = dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?")
|
||||
if resp == 0:
|
||||
if dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?") == 0:
|
||||
return
|
||||
|
||||
# first stop any db sync
|
||||
window('emby_shouldStop', value="true")
|
||||
window('plex_shouldStop', value="true")
|
||||
count = 10
|
||||
while window('emby_dbScan') == "true":
|
||||
while window('plex_dbScan') == "true":
|
||||
logMsg("PLEX", "Sync is running, will retry: %s..." % count)
|
||||
count -= 1
|
||||
if count == 0:
|
||||
|
@ -442,7 +472,7 @@ def reset():
|
|||
deleteNodes()
|
||||
|
||||
# Wipe the kodi databases
|
||||
logMsg("EMBY", "Resetting the Kodi video database.", 0)
|
||||
logMsg("Plex", "Resetting the Kodi video database.", 0)
|
||||
connection = kodiSQL('video')
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||
|
@ -455,7 +485,7 @@ def reset():
|
|||
cursor.close()
|
||||
|
||||
if settings('enableMusic') == "true":
|
||||
logMsg("EMBY", "Resetting the Kodi music database.")
|
||||
logMsg("Plex", "Resetting the Kodi music database.")
|
||||
connection = kodiSQL('music')
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||
|
@ -467,8 +497,8 @@ def reset():
|
|||
connection.commit()
|
||||
cursor.close()
|
||||
|
||||
# Wipe the emby database
|
||||
logMsg("EMBY", "Resetting the Emby database.", 0)
|
||||
# Wipe the Plex database
|
||||
logMsg("Plex", "Resetting the Emby database.", 0)
|
||||
connection = kodiSQL('emby')
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||
|
@ -483,21 +513,25 @@ def reset():
|
|||
cursor.close()
|
||||
|
||||
# Offer to wipe cached thumbnails
|
||||
resp = dialog.yesno("Warning", "Removed all cached artwork?")
|
||||
resp = dialog.yesno("Warning", "Remove all cached artwork?")
|
||||
if resp:
|
||||
logMsg("EMBY", "Resetting all cached artwork.", 0)
|
||||
# Remove all existing textures first
|
||||
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
|
||||
path = tryDecode(xbmc.translatePath("special://thumbnails/"))
|
||||
if xbmcvfs.exists(path):
|
||||
allDirs, allFiles = xbmcvfs.listdir(path)
|
||||
for dir in allDirs:
|
||||
allDirs, allFiles = xbmcvfs.listdir(path+dir)
|
||||
for file in allFiles:
|
||||
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:
|
||||
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
|
||||
connection = kodiSQL('texture')
|
||||
cursor = connection.cursor()
|
||||
|
@ -509,8 +543,8 @@ def reset():
|
|||
cursor.execute("DELETE FROM " + tableName)
|
||||
connection.commit()
|
||||
cursor.close()
|
||||
|
||||
# reset the install run flag
|
||||
|
||||
# reset the install run flag
|
||||
settings('SyncInstallRunDone', value="false")
|
||||
|
||||
# Remove emby info
|
||||
|
@ -518,9 +552,9 @@ def reset():
|
|||
if resp:
|
||||
# Delete the settings
|
||||
addon = xbmcaddon.Addon()
|
||||
addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
|
||||
addondir = tryDecode(xbmc.translatePath(addon.getAddonInfo('profile')))
|
||||
dataPath = "%ssettings.xml" % addondir
|
||||
xbmcvfs.delete(dataPath.encode('utf-8'))
|
||||
xbmcvfs.delete(tryEncode(dataPath))
|
||||
logMsg("PLEX", "Deleting: settings.xml", 1)
|
||||
|
||||
dialog.ok(
|
||||
|
@ -532,7 +566,7 @@ def profiling(sortby="cumulative"):
|
|||
# Will print results to Kodi log
|
||||
def decorator(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
|
||||
pr = cProfile.Profile()
|
||||
|
||||
pr.enable()
|
||||
|
@ -575,8 +609,8 @@ def normalize_nodes(text):
|
|||
# Remove dots from the last character as windows can not have directories
|
||||
# with dots at the end
|
||||
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
|
||||
|
||||
def normalize_string(text):
|
||||
|
@ -594,7 +628,7 @@ def normalize_string(text):
|
|||
# Remove dots from the last character as windows can not have directories
|
||||
# with dots at the end
|
||||
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
|
||||
|
||||
|
@ -619,7 +653,7 @@ def guisettingsXML():
|
|||
"""
|
||||
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
|
||||
|
||||
try:
|
||||
|
@ -678,7 +712,7 @@ def advancedSettingsXML():
|
|||
usetags : 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
|
||||
|
||||
try:
|
||||
|
@ -708,7 +742,7 @@ def advancedSettingsXML():
|
|||
|
||||
def sourcesXML():
|
||||
# To make Master lock compatible
|
||||
path = xbmc.translatePath("special://profile/").decode('utf-8')
|
||||
path = tryDecode(xbmc.translatePath("special://profile/"))
|
||||
xmlpath = "%ssources.xml" % path
|
||||
|
||||
try:
|
||||
|
@ -717,7 +751,7 @@ def sourcesXML():
|
|||
root = etree.Element('sources')
|
||||
else:
|
||||
root = xmlparse.getroot()
|
||||
|
||||
|
||||
|
||||
video = root.find('video')
|
||||
if video is None:
|
||||
|
@ -729,7 +763,7 @@ def sourcesXML():
|
|||
for source in root.findall('.//path'):
|
||||
if source.text == "smb://":
|
||||
count -= 1
|
||||
|
||||
|
||||
if count == 0:
|
||||
# sources already set
|
||||
break
|
||||
|
@ -749,7 +783,7 @@ def sourcesXML():
|
|||
|
||||
def passwordsXML():
|
||||
# To add network credentials
|
||||
path = xbmc.translatePath("special://userdata/").decode('utf-8')
|
||||
path = tryDecode(xbmc.translatePath("special://userdata/"))
|
||||
xmlpath = "%spasswords.xml" % path
|
||||
logMsg('passwordsXML', 'Path to passwords.xml: %s' % xmlpath, 1)
|
||||
|
||||
|
@ -775,9 +809,7 @@ def passwordsXML():
|
|||
|
||||
elif option == 1:
|
||||
# User selected remove
|
||||
iterator = root.getiterator('passwords')
|
||||
|
||||
for paths in iterator:
|
||||
for paths in root.getiterator('passwords'):
|
||||
for path in paths:
|
||||
if path.find('.//from').text == "smb://%s/" % credentials:
|
||||
paths.remove(path)
|
||||
|
@ -787,8 +819,8 @@ def passwordsXML():
|
|||
etree.ElementTree(root).write(xmlpath)
|
||||
break
|
||||
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="")
|
||||
xbmcgui.Dialog().notification(
|
||||
heading='PlexKodiConnect',
|
||||
|
@ -842,7 +874,7 @@ def passwordsXML():
|
|||
# Force Kodi to see the credentials without restarting
|
||||
xbmcvfs.exists(topath)
|
||||
|
||||
# Add credentials
|
||||
# Add credentials
|
||||
settings('networkCreds', value="%s" % server)
|
||||
logMsg("PLEX", "Added server: %s to passwords.xml" % server, 1)
|
||||
# Prettify and write to file
|
||||
|
@ -850,7 +882,7 @@ def passwordsXML():
|
|||
indent(root)
|
||||
except: pass
|
||||
etree.ElementTree(root).write(xmlpath)
|
||||
|
||||
|
||||
# dialog.notification(
|
||||
# heading="PlexKodiConnect",
|
||||
# message="Added to passwords.xml",
|
||||
|
@ -862,7 +894,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
|
|||
"""
|
||||
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":
|
||||
plname = "%s - %s" % (tagname, 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)
|
||||
|
||||
# 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)
|
||||
xbmcvfs.mkdirs(path.encode('utf-8'))
|
||||
xbmcvfs.mkdirs(tryEncode(path))
|
||||
|
||||
# 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)
|
||||
if delete:
|
||||
xbmcvfs.delete(xsppath.encode('utf-8'))
|
||||
xbmcvfs.delete(tryEncode(xsppath))
|
||||
logMsg("PLEX", "Successfully removed playlist: %s." % tagname, 1)
|
||||
|
||||
|
||||
return
|
||||
|
||||
# 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)
|
||||
try:
|
||||
f = xbmcvfs.File(xsppath.encode('utf-8'), 'wb')
|
||||
f = xbmcvfs.File(tryEncode(xsppath), 'wb')
|
||||
except:
|
||||
logMsg("Plex", "Failed to create playlist: %s" % xsppath, -1)
|
||||
return
|
||||
else:
|
||||
f.write((
|
||||
f.write(tryEncode(
|
||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
|
||||
'<smartplaylist type="%s">\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'
|
||||
'</rule>\n'
|
||||
'</smartplaylist>\n'
|
||||
% (itemtypes.get(mediatype, mediatype), plname, tagname))
|
||||
.encode('utf-8'))
|
||||
% (itemtypes.get(mediatype, mediatype), plname, tagname)))
|
||||
f.close()
|
||||
logMsg("Plex", "Successfully added playlist: %s" % tagname)
|
||||
|
||||
def deletePlaylists():
|
||||
|
||||
# Clean up the playlists
|
||||
path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
|
||||
dirs, files = xbmcvfs.listdir(path.encode('utf-8'))
|
||||
path = tryDecode(xbmc.translatePath("special://profile/playlists/video/"))
|
||||
dirs, files = xbmcvfs.listdir(tryEncode(path))
|
||||
for file in files:
|
||||
if file.decode('utf-8').startswith('Plex'):
|
||||
xbmcvfs.delete(("%s%s" % (path, file.decode('utf-8'))).encode('utf-8'))
|
||||
if tryDecode(file).startswith('Plex'):
|
||||
xbmcvfs.delete(tryEncode("%s%s" % (path, tryDecode(file))))
|
||||
|
||||
def deleteNodes():
|
||||
|
||||
# Clean up video nodes
|
||||
import shutil
|
||||
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
||||
dirs, files = xbmcvfs.listdir(path.encode('utf-8'))
|
||||
path = tryDecode(xbmc.translatePath("special://profile/library/video/"))
|
||||
dirs, files = xbmcvfs.listdir(tryEncode(path))
|
||||
for dir in dirs:
|
||||
if dir.decode('utf-8').startswith('Plex'):
|
||||
if tryDecode(dir).startswith('Plex'):
|
||||
try:
|
||||
shutil.rmtree("%s%s" % (path, dir.decode('utf-8')))
|
||||
shutil.rmtree("%s%s" % (path, tryDecode(dir)))
|
||||
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:
|
||||
if file.decode('utf-8').startswith('plex'):
|
||||
if tryDecode(file).startswith('plex'):
|
||||
try:
|
||||
xbmcvfs.delete(("%s%s" % (path, file.decode('utf-8'))).encode('utf-8'))
|
||||
xbmcvfs.delete(tryEncode("%s%s" % (path, tryDecode(file))))
|
||||
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:
|
||||
return text.decode(encoding,"ignore")
|
||||
except:
|
||||
return text
|
||||
uniString = uniString.encode(encoding, "ignore")
|
||||
except TypeError:
|
||||
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]
|
||||
|
||||
window = utils.window
|
||||
kodiversion = self.kodiversion
|
||||
|
||||
if viewtype == "mixed":
|
||||
dirname = "%s-%s" % (viewid, mediatype)
|
||||
else:
|
||||
dirname = viewid
|
||||
|
||||
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
||||
nodepath = xbmc.translatePath(
|
||||
"special://profile/library/video/Plex-%s/" % dirname).decode('utf-8')
|
||||
path = utils.tryDecode(xbmc.translatePath(
|
||||
"special://profile/library/video/"))
|
||||
nodepath = utils.tryDecode(xbmc.translatePath(
|
||||
"special://profile/library/video/Plex-%s/" % dirname))
|
||||
|
||||
# Verify the video directory
|
||||
# KODI BUG
|
||||
|
@ -71,20 +71,22 @@ class VideoNodes(object):
|
|||
# so try creating a file
|
||||
if utils.IfExists(path) is False:
|
||||
shutil.copytree(
|
||||
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
|
||||
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
|
||||
src=utils.tryDecode(xbmc.translatePath(
|
||||
"special://xbmc/system/library/video")),
|
||||
dst=utils.tryDecode(xbmc.translatePath(
|
||||
"special://profile/library/video")))
|
||||
|
||||
# Create the node directory
|
||||
if mediatype != "photo":
|
||||
if mediatype != "photos":
|
||||
if utils.IfExists(nodepath) is False:
|
||||
# folder does not exist yet
|
||||
self.logMsg('Creating folder %s' % nodepath, 1)
|
||||
xbmcvfs.mkdirs(nodepath.encode('utf-8'))
|
||||
xbmcvfs.mkdirs(utils.tryEncode(nodepath))
|
||||
if delete:
|
||||
dirs, files = xbmcvfs.listdir(nodepath.encode('utf-8'))
|
||||
dirs, files = xbmcvfs.listdir(utils.tryEncode(nodepath))
|
||||
for file in files:
|
||||
xbmcvfs.delete(
|
||||
(nodepath + file.decode('utf-8')).encode('utf-8'))
|
||||
xbmcvfs.delete(utils.tryEncode(
|
||||
(nodepath + utils.tryDecode(file))))
|
||||
|
||||
self.logMsg("Sucessfully removed videonode: %s."
|
||||
% tagname, 1)
|
||||
|
@ -96,16 +98,16 @@ class VideoNodes(object):
|
|||
path = "library://video/Plex-%s/" % dirname
|
||||
for i in range(1, indexnumber):
|
||||
# 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
|
||||
|
||||
if mediatype == "photo":
|
||||
if mediatype == "photos":
|
||||
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
|
||||
if not mediatype == "photo":
|
||||
if not mediatype == "photos":
|
||||
if viewtype == "mixed":
|
||||
specialtag = "%s-%s" % (tagname, mediatype)
|
||||
root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0)
|
||||
|
@ -215,14 +217,14 @@ class VideoNodes(object):
|
|||
label = stringid
|
||||
|
||||
# Set window properties
|
||||
if (mediatype == "homevideos" or mediatype == "photo") and nodetype == "all":
|
||||
if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all":
|
||||
# Custom query
|
||||
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=browsecontent&type=%s"
|
||||
% (tagname, mediatype))
|
||||
elif (mediatype == "homevideos" or mediatype == "photo"):
|
||||
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=browseplex&type=%s"
|
||||
% (viewid, mediatype))
|
||||
elif (mediatype == "homevideos" or mediatype == "photos"):
|
||||
# Custom query
|
||||
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=browsecontent&type=%s&folderid=%s"
|
||||
% (tagname, mediatype, nodetype))
|
||||
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=browseplex&type=%s&folderid=%s"
|
||||
% (viewid, mediatype, nodetype))
|
||||
elif nodetype == "nextepisodes":
|
||||
# Custom query
|
||||
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=nextup&limit=%s" % (tagname, limit)
|
||||
|
@ -231,7 +233,7 @@ class VideoNodes(object):
|
|||
# Custom query
|
||||
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=recentepisodes&type=%s&tagname=%s&limit=%s"
|
||||
% (viewid, mediatype, tagname, limit))
|
||||
elif kodiversion == 14 and nodetype == "inprogressepisodes":
|
||||
elif self.kodiversion == 14 and nodetype == "inprogressepisodes":
|
||||
# Custom query
|
||||
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=inprogressepisodes&limit=%s" % (tagname, limit)
|
||||
elif nodetype == 'ondeck':
|
||||
|
@ -245,10 +247,14 @@ class VideoNodes(object):
|
|||
else:
|
||||
path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype)
|
||||
|
||||
if mediatype == "photo":
|
||||
if mediatype == "photos":
|
||||
windowpath = "ActivateWindow(Pictures,%s,return)" % path
|
||||
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":
|
||||
|
||||
|
@ -257,24 +263,24 @@ class VideoNodes(object):
|
|||
else:
|
||||
templabel = label
|
||||
|
||||
embynode = "Emby.nodes.%s" % indexnumber
|
||||
embynode = "Plex.nodes.%s" % indexnumber
|
||||
window('%s.title' % embynode, value=templabel)
|
||||
window('%s.path' % embynode, value=windowpath)
|
||||
window('%s.content' % embynode, value=path)
|
||||
window('%s.type' % embynode, value=mediatype)
|
||||
else:
|
||||
embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
|
||||
embynode = "Plex.nodes.%s.%s" % (indexnumber, nodetype)
|
||||
window('%s.title' % embynode, value=label)
|
||||
window('%s.path' % embynode, value=windowpath)
|
||||
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
|
||||
# to be created.
|
||||
# To do: add our photos nodes to kodi picture sources somehow
|
||||
continue
|
||||
|
||||
if xbmcvfs.exists(nodeXML.encode('utf-8')):
|
||||
if xbmcvfs.exists(utils.tryEncode(nodeXML)):
|
||||
# Don't recreate xml if already exists
|
||||
continue
|
||||
|
||||
|
@ -298,8 +304,12 @@ class VideoNodes(object):
|
|||
elif nodetype == "recent":
|
||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
if utils.settings('MovieShowWatched') == 'false':
|
||||
rule = etree.SubElement(root,
|
||||
'rule',
|
||||
{'field': "playcount",
|
||||
'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
|
||||
elif nodetype == "inprogress":
|
||||
etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
|
||||
|
@ -358,19 +368,26 @@ class VideoNodes(object):
|
|||
|
||||
window = utils.window
|
||||
|
||||
tagname = tagname.encode('utf-8')
|
||||
tagname = utils.tryEncode(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)
|
||||
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
|
||||
if not xbmcvfs.exists(nodepath):
|
||||
# We need to copy over the default items
|
||||
shutil.copytree(
|
||||
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
|
||||
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
|
||||
src=utils.tryDecode(xbmc.translatePath(
|
||||
"special://xbmc/system/library/video")),
|
||||
dst=utils.tryDecode(xbmc.translatePath(
|
||||
"special://profile/library/video")))
|
||||
xbmcvfs.exists(path)
|
||||
|
||||
labels = {
|
||||
|
@ -380,7 +397,7 @@ class VideoNodes(object):
|
|||
'channels': 30173
|
||||
}
|
||||
label = utils.language(labels[tagname])
|
||||
embynode = "Emby.nodes.%s" % indexnumber
|
||||
embynode = "Plex.nodes.%s" % indexnumber
|
||||
window('%s.title' % embynode, value=label)
|
||||
window('%s.path' % embynode, value=windowpath)
|
||||
window('%s.content' % embynode, value=path)
|
||||
|
@ -409,7 +426,7 @@ class VideoNodes(object):
|
|||
window = utils.window
|
||||
|
||||
self.logMsg("Clearing nodes properties.", 1)
|
||||
embyprops = window('Emby.nodes.total')
|
||||
plexprops = window('Plex.nodes.total')
|
||||
propnames = [
|
||||
|
||||
"index","path","title","content",
|
||||
|
@ -424,8 +441,8 @@ class VideoNodes(object):
|
|||
"inprogressepisodes.content","inprogressepisodes.path"
|
||||
]
|
||||
|
||||
if embyprops:
|
||||
totalnodes = int(embyprops)
|
||||
if plexprops:
|
||||
totalnodes = int(plexprops)
|
||||
for i in range(totalnodes):
|
||||
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:
|
||||
self.logMsg('No message type, dropping message: %s' % message, -1)
|
||||
return False
|
||||
self.logMsg('Received message from PMS server: %s' % message, 2)
|
||||
# Drop everything we're not interested in
|
||||
if typus not in ('playing', 'timeline'):
|
||||
return True
|
||||
|
@ -87,17 +88,16 @@ class WebSocket(threading.Thread):
|
|||
uri = "%s/:/websockets/notifications" % server
|
||||
if token:
|
||||
uri += '?X-Plex-Token=%s' % token
|
||||
return uri
|
||||
|
||||
def run(self):
|
||||
log = self.logMsg
|
||||
# Currently not working due to missing SSL environment
|
||||
sslopt = {}
|
||||
if utils.settings('sslverify') == "false":
|
||||
sslopt["cert_reqs"] = ssl.CERT_NONE
|
||||
return uri, sslopt
|
||||
|
||||
def run(self):
|
||||
log = self.logMsg
|
||||
log("----===## Starting WebSocketClient ##===----", 0)
|
||||
|
||||
counter = 0
|
||||
threadStopped = self.threadStopped
|
||||
threadSuspended = self.threadSuspended
|
||||
while not threadStopped():
|
||||
|
@ -122,7 +122,7 @@ class WebSocket(threading.Thread):
|
|||
pass
|
||||
except websocket.WebSocketConnectionClosedException:
|
||||
log("Connection closed, (re)connecting", 0)
|
||||
uri = self.getUri()
|
||||
uri, sslopt = self.getUri()
|
||||
try:
|
||||
# Low timeout - let's us shut this thread down!
|
||||
self.ws = websocket.create_connection(
|
||||
|
@ -131,8 +131,15 @@ class WebSocket(threading.Thread):
|
|||
sslopt=sslopt,
|
||||
enable_multithread=True)
|
||||
except IOError:
|
||||
# Server is probably offline
|
||||
log("Error connecting", 0)
|
||||
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)
|
||||
except websocket.WebSocketTimeoutException:
|
||||
log("timeout while connecting, trying again", 0)
|
||||
|
@ -142,6 +149,8 @@ class WebSocket(threading.Thread):
|
|||
log("Unknown exception encountered in connecting: %s" % e)
|
||||
self.ws = None
|
||||
xbmc.sleep(1000)
|
||||
else:
|
||||
counter = 0
|
||||
except Exception as e:
|
||||
log("Unknown exception encountered: %s" % e)
|
||||
try:
|
||||
|
|
|
@ -1,37 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<settings>
|
||||
<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 id="ipaddress" label="30000" type="text" default="" />
|
||||
<setting id="port" label="30030" type="number" default="32400" />
|
||||
<setting label="39068" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=enterPMS)" option="close" /><!-- Manually enter Plex Media Server IP -->
|
||||
<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="https" label="30243" type="bool" default="false" />
|
||||
<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)" />
|
||||
<!-- Secondary address -->
|
||||
<setting id="altip" label="30502" type="bool" default="false" visible="false"/>
|
||||
<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 type="sep" text=""/>
|
||||
|
||||
<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="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="pathsub" type="bool" visible="false" default="false" />
|
||||
<setting label="39024" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=reConnect)" option="close" />
|
||||
</category>
|
||||
|
||||
<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 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="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="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="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 type="lsep" label="39052" /><!-- Background Sync -->
|
||||
<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="dbSyncScreensaver" type="bool" label="39062" default="false" /><!--Sync when screensaver is deactivated-->
|
||||
|
||||
<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="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 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="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="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 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="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="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 label="30516"><!-- Playback -->
|
||||
<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="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" />
|
||||
|
@ -93,11 +93,12 @@
|
|||
<setting id="resumeJumpBack" type="slider" label="30521" default="10" range="0,1,120" option="int" />
|
||||
<setting type="sep" />
|
||||
<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="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="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="networkCreds" type="text" visible="false" default="" />
|
||||
</category>
|
||||
|
@ -125,12 +126,17 @@
|
|||
|
||||
</category>
|
||||
|
||||
<category label="39045"><!-- Appearance Tweaks -->
|
||||
<category label="39073"><!-- Appearance Tweaks -->
|
||||
<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="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="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 label="30022"><!-- Advanced -->
|
||||
|
|
130
service.py
130
service.py
|
@ -4,7 +4,6 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
import Queue
|
||||
|
||||
import xbmc
|
||||
|
@ -14,19 +13,31 @@ import xbmcgui
|
|||
###############################################################################
|
||||
|
||||
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||
addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
||||
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)
|
||||
|
||||
###############################################################################
|
||||
|
||||
import utils
|
||||
import userclient
|
||||
import clientinfo
|
||||
import initialsetup
|
||||
import kodimonitor
|
||||
import librarysync
|
||||
import player
|
||||
import utils
|
||||
import videonodes
|
||||
import websocket_client as wsc
|
||||
import downloadutils
|
||||
|
@ -59,11 +70,9 @@ class Service():
|
|||
logLevel = self.getLogLevel()
|
||||
self.monitor = xbmc.Monitor()
|
||||
|
||||
window('emby_logLevel', value=str(logLevel))
|
||||
window('emby_kodiProfile', value=xbmc.translatePath("special://profile"))
|
||||
window('emby_pluginpath', value=utils.settings('useDirectPaths'))
|
||||
|
||||
self.runPlexCompanion = utils.settings('plexCompanion')
|
||||
window('plex_logLevel', value=str(logLevel))
|
||||
window('plex_kodiProfile', value=xbmc.translatePath("special://profile"))
|
||||
window('plex_pluginpath', value=utils.settings('useDirectPaths'))
|
||||
|
||||
# Initial logging
|
||||
log("======== START %s ========" % self.addonName, 0)
|
||||
|
@ -76,16 +85,17 @@ class Service():
|
|||
# Reset window props for profile switch
|
||||
properties = [
|
||||
|
||||
"emby_online", "emby_serverStatus", "emby_onWake",
|
||||
"emby_syncRunning", "emby_dbCheck", "emby_kodiScan",
|
||||
"emby_shouldStop", "currUserId", "emby_dbScan", "emby_sessionId",
|
||||
"emby_initialScan", "emby_customplaylist", "emby_playbackProps",
|
||||
"plex_online", "plex_serverStatus", "plex_onWake",
|
||||
"plex_dbCheck", "plex_kodiScan",
|
||||
"plex_shouldStop", "currUserId", "plex_dbScan",
|
||||
"plex_initialScan", "plex_customplaylist", "plex_playbackProps",
|
||||
"plex_runLibScan", "plex_username", "pms_token", "plex_token",
|
||||
"pms_server", "plex_machineIdentifier", "plex_servername",
|
||||
"plex_authenticated", "PlexUserImage", "useDirectPaths",
|
||||
"replaceSMB", "remapSMB", "remapSMBmovieOrg", "remapSMBtvOrg",
|
||||
"remapSMBmusicOrg", "remapSMBmovieNew", "remapSMBtvNew",
|
||||
"remapSMBmusicNew", "suspend_LibraryThread", "plex_terminateNow",
|
||||
"remapSMBmusicNew", "remapSMBphotoOrg", "remapSMBphotoNew",
|
||||
"suspend_LibraryThread", "plex_terminateNow",
|
||||
"kodiplextimeoffset", "countError", "countUnauthorized"
|
||||
]
|
||||
for prop in properties:
|
||||
|
@ -93,9 +103,9 @@ class Service():
|
|||
|
||||
# Clear video nodes properties
|
||||
videonodes.VideoNodes().clearProperties()
|
||||
|
||||
|
||||
# Set the minimum database version
|
||||
window('emby_minDBVersion', value="1.1.4")
|
||||
window('plex_minDBVersion', value="1.1.5")
|
||||
|
||||
def getLogLevel(self):
|
||||
try:
|
||||
|
@ -127,61 +137,28 @@ class Service():
|
|||
user = userclient.UserClient()
|
||||
ws = wsc.WebSocket(queue)
|
||||
library = librarysync.LibrarySync(queue)
|
||||
kplayer = player.Player()
|
||||
xplayer = xbmc.Player()
|
||||
plx = PlexAPI.PlexAPI()
|
||||
|
||||
# Sync and progress report
|
||||
lastProgressUpdate = datetime.today()
|
||||
|
||||
counter = 0
|
||||
while not monitor.abortRequested():
|
||||
|
||||
if window('emby_kodiProfile') != kodiProfile:
|
||||
if window('plex_kodiProfile') != kodiProfile:
|
||||
# Profile change happened, terminate this thread and others
|
||||
log("Kodi profile was: %s and changed to: %s. Terminating old Emby thread."
|
||||
% (kodiProfile, utils.window('emby_kodiProfile')), 1)
|
||||
|
||||
log("Kodi profile was: %s and changed to: %s. Terminating old "
|
||||
"PlexKodiConnect thread."
|
||||
% (kodiProfile, utils.window('plex_kodiProfile')), 1)
|
||||
break
|
||||
|
||||
|
||||
# Before proceeding, need to make sure:
|
||||
# 1. Server is online
|
||||
# 2. User is set
|
||||
# 3. User has access to the server
|
||||
|
||||
if window('emby_online') == "true":
|
||||
# Emby server is online
|
||||
if window('plex_online') == "true":
|
||||
# Plex server is online
|
||||
# Verify if user is set and has access to the server
|
||||
if (user.currUser is not None) and user.HasAccess:
|
||||
# If an item is playing
|
||||
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:
|
||||
if not self.kodimonitor_running:
|
||||
# Start up events
|
||||
self.warn_auth = True
|
||||
if connectMsg and self.welcome_msg:
|
||||
|
@ -194,8 +171,7 @@ class Service():
|
|||
time=2000,
|
||||
sound=False)
|
||||
# Start monitoring kodi events
|
||||
if not self.kodimonitor_running:
|
||||
self.kodimonitor_running = kodimonitor.KodiMonitor()
|
||||
self.kodimonitor_running = kodimonitor.KodiMonitor()
|
||||
|
||||
# Start the Websocket Client
|
||||
if not self.websocket_running:
|
||||
|
@ -206,8 +182,7 @@ class Service():
|
|||
self.library_running = True
|
||||
library.start()
|
||||
# Start the Plex Companion thread
|
||||
if not self.plexCompanion_running and \
|
||||
self.runPlexCompanion == "true":
|
||||
if not self.plexCompanion_running:
|
||||
self.plexCompanion_running = True
|
||||
plexCompanion = PlexCompanion.PlexCompanion()
|
||||
plexCompanion.start()
|
||||
|
@ -224,7 +199,7 @@ class Service():
|
|||
# Verify access with an API call
|
||||
user.hasAccess()
|
||||
|
||||
if window('emby_online') != "true":
|
||||
if window('plex_online') != "true":
|
||||
# Server went offline
|
||||
break
|
||||
|
||||
|
@ -233,7 +208,7 @@ class Service():
|
|||
break
|
||||
xbmc.sleep(50)
|
||||
else:
|
||||
# Wait until Emby server is online
|
||||
# Wait until Plex server is online
|
||||
# or Kodi is shut down.
|
||||
while not monitor.abortRequested():
|
||||
server = user.getServer()
|
||||
|
@ -244,11 +219,10 @@ class Service():
|
|||
# Server is offline or cannot be reached
|
||||
# Alert the user and suppress future warning
|
||||
if self.server_online:
|
||||
log("Server is offline.", 1)
|
||||
window('emby_online', value="false")
|
||||
log("Server is offline.", -1)
|
||||
window('plex_online', value="false")
|
||||
# Suspend threads
|
||||
window('suspend_LibraryThread', value='true')
|
||||
|
||||
xbmcgui.Dialog().notification(
|
||||
heading=lang(33001),
|
||||
message="%s %s"
|
||||
|
@ -257,8 +231,17 @@ class Service():
|
|||
"plexkodiconnect/icon.png",
|
||||
sound=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:
|
||||
# Server is online
|
||||
counter = 0
|
||||
if not self.server_online:
|
||||
# Server was offline when Kodi started.
|
||||
# Wait for server to be fully established.
|
||||
|
@ -271,11 +254,11 @@ class Service():
|
|||
message=lang(33003),
|
||||
icon="special://home/addons/plugin.video."
|
||||
"plexkodiconnect/icon.png",
|
||||
time=2000,
|
||||
time=5000,
|
||||
sound=False)
|
||||
self.server_online = True
|
||||
log("Server %s is online and ready." % server, 1)
|
||||
window('emby_online', value="true")
|
||||
window('plex_online', value="true")
|
||||
if window('plex_authenticated') == 'true':
|
||||
# Server got offline when we were authenticated.
|
||||
# Hence resume threads
|
||||
|
@ -288,12 +271,11 @@ class Service():
|
|||
|
||||
break
|
||||
|
||||
if monitor.waitForAbort(1):
|
||||
if monitor.waitForAbort(2):
|
||||
# Abort was requested while waiting.
|
||||
break
|
||||
xbmc.sleep(50)
|
||||
|
||||
if monitor.waitForAbort(1):
|
||||
if monitor.waitForAbort(0.05):
|
||||
# Abort was requested while waiting. We should exit
|
||||
break
|
||||
|
||||
|
@ -335,6 +317,6 @@ delay = int(utils.settings('startupDelay'))
|
|||
xbmc.log("Delaying Plex startup by: %s sec..." % delay)
|
||||
if delay and xbmc.Monitor().waitForAbort(delay):
|
||||
# Start the service
|
||||
xbmc.log("Abort requested while waiting. Emby for kodi not started.")
|
||||
xbmc.log("Abort requested while waiting. PKC not started.")
|
||||
else:
|
||||
Service().ServiceEntryPoint()
|
||||
|
|
Loading…
Reference in a new issue