Merge branch 'master' into translations
This commit is contained in:
commit
bc36231454
15 changed files with 372 additions and 429 deletions
67
README.md
67
README.md
|
@ -1,5 +1,5 @@
|
||||||
[![stable version](https://img.shields.io/badge/stable_version-1.8.18-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
|
[![stable version](https://img.shields.io/badge/stable_version-1.8.18-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
|
||||||
[![beta version](https://img.shields.io/badge/beta_version-2.0.16-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
|
[![beta version](https://img.shields.io/badge/beta_version-2.0.18-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
|
||||||
|
|
||||||
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
|
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
|
||||||
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)
|
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)
|
||||||
|
@ -15,33 +15,28 @@ PKC combines the best of Kodi - ultra smooth navigation, beautiful and highly cu
|
||||||
|
|
||||||
Have a look at [some screenshots](https://github.com/croneter/PlexKodiConnect/wiki/Some-PKC-Screenshots) to see what's possible.
|
Have a look at [some screenshots](https://github.com/croneter/PlexKodiConnect/wiki/Some-PKC-Screenshots) to see what's possible.
|
||||||
|
|
||||||
### UPDATE YOUR PKC REPO TO RECEIVE UPDATES!
|
### Update Your PKC Repo to Receive Updates!
|
||||||
|
|
||||||
Unfortunately, the PKC Kodi repository had to move because it stopped working (thanks https://bintray.com). If you installed PKC before December 15, 2017, you need to [**MANUALLY** update the repo once](https://github.com/croneter/PlexKodiConnect/wiki/Update-PKC-Repository).
|
Unfortunately, the PKC Kodi repository had to move because it stopped working (thanks https://bintray.com). If you installed PKC before December 15, 2017, you need to [**MANUALLY** update the repo once](https://github.com/croneter/PlexKodiConnect/wiki/Update-PKC-Repository).
|
||||||
|
|
||||||
|
|
||||||
### Please Help Translating
|
|
||||||
|
|
||||||
Please help translate PlexKodiConnect into your language: [Transifex.com](https://www.transifex.com/croneter/pkc)
|
|
||||||
|
|
||||||
|
|
||||||
### Content
|
### Content
|
||||||
* [**Warning**](#warning)
|
|
||||||
* [**What does PKC do?**](#what-does-pkc-do)
|
|
||||||
* [**PKC Features**](#pkc-features)
|
|
||||||
* [**Download and Installation**](#download-and-installation)
|
* [**Download and Installation**](#download-and-installation)
|
||||||
|
* [**What does PKC do?**](#what-does-pkc-do)
|
||||||
|
* [**Warning**](#warning)
|
||||||
|
* [**PKC Features**](#pkc-features)
|
||||||
* [**Additional Artwork**](#additional-artwork)
|
* [**Additional Artwork**](#additional-artwork)
|
||||||
* [**Important notes**](#important-notes)
|
|
||||||
* [**Donations**](#donations)
|
* [**Donations**](#donations)
|
||||||
* [**Request a New Feature**](#request-a-new-feature)
|
* [**Request a New Feature**](#request-a-new-feature)
|
||||||
* [**Known Larger Issues**](#known-larger-issues)
|
* [**Issues and Bugs**](#issues-and-bugs)
|
||||||
* [**Issues being worked on**](#issues-being-worked-on)
|
|
||||||
* [**Credits**](#credits)
|
* [**Credits**](#credits)
|
||||||
|
|
||||||
### Warning
|
### Download and Installation
|
||||||
Use at your own risk! This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases as this plugin directly changes them. Don't worry if you want Plex to manage all your media (like you should ;-)).
|
|
||||||
|
|
||||||
Some people argue that PKC is 'hacky' because of the way it directly accesses the Kodi database. See [here for a more thorough discussion](https://github.com/croneter/PlexKodiConnect/wiki/Is-PKC-'hacky'%3F).
|
Install PKC via the PlexKodiConnect Kodi repository download button just below (do NOT use the standard GitHub download!). See the [github wiki installation manual](https://github.com/croneter/PlexKodiConnect/wiki/Installation) for a detailed guide. Please use the stable version except if you really know what you're doing. Kodi will update PKC automatically.
|
||||||
|
|
||||||
|
| Stable version | Beta version |
|
||||||
|
|----------------|--------------|
|
||||||
|
| [![stable version](https://img.shields.io/badge/stable_version-latest-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip) | [![beta version](https://img.shields.io/badge/beta_version-latest-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip) |
|
||||||
|
|
||||||
### What does PKC do?
|
### What does PKC do?
|
||||||
PKC synchronizes your media from your Plex server to the native Kodi database. Hence:
|
PKC synchronizes your media from your Plex server to the native Kodi database. Hence:
|
||||||
|
@ -51,8 +46,14 @@ PKC synchronizes your media from your Plex server to the native Kodi database. H
|
||||||
- Automatically get additional artwork (more than Plex offers)
|
- Automatically get additional artwork (more than Plex offers)
|
||||||
- Enjoy Plex features using the Kodi interface
|
- Enjoy Plex features using the Kodi interface
|
||||||
|
|
||||||
|
### Warning
|
||||||
|
Use at your own risk! This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases as this plugin directly changes them. Don't worry if you want Plex to manage all your media (like you should ;-)).
|
||||||
|
|
||||||
|
Some people argue that PKC is 'hacky' because of the way it directly accesses the Kodi database. See [here for a more thorough discussion](https://github.com/croneter/PlexKodiConnect/wiki/Is-PKC-'hacky'%3F).
|
||||||
|
|
||||||
### PKC Features
|
### PKC Features
|
||||||
|
|
||||||
|
- Support of Kodi 18 Leia (and Kodi 17 Krypton)
|
||||||
- [Amazon Alexa voice recognition](https://www.plex.tv/apps/streaming-devices/amazon-alexa)
|
- [Amazon Alexa voice recognition](https://www.plex.tv/apps/streaming-devices/amazon-alexa)
|
||||||
- [Plex Watch Later / Plex It!](https://support.plex.tv/hc/en-us/sections/200211783-Plex-It-)
|
- [Plex Watch Later / Plex It!](https://support.plex.tv/hc/en-us/sections/200211783-Plex-It-)
|
||||||
- [Plex Companion](https://support.plex.tv/hc/en-us/sections/200276908-Plex-Companion): fling Plex media (or anything else) from other Plex devices to PlexKodiConnect
|
- [Plex Companion](https://support.plex.tv/hc/en-us/sections/200276908-Plex-Companion): fling Plex media (or anything else) from other Plex devices to PlexKodiConnect
|
||||||
|
@ -75,28 +76,13 @@ PKC synchronizes your media from your Plex server to the native Kodi database. H
|
||||||
+ Norwegian, thanks @mjorud
|
+ Norwegian, thanks @mjorud
|
||||||
+ Portuguese, thanks @goncalo532
|
+ Portuguese, thanks @goncalo532
|
||||||
+ Russian, thanks @UncleStark
|
+ Russian, thanks @UncleStark
|
||||||
+ [Please help translating](https://www.transifex.com/croneter/pkc)
|
+ Hungarian, thanks @savage93
|
||||||
|
+ [You can easily help to translate PKC!](https://www.transifex.com/croneter/pkc)
|
||||||
### Download and Installation
|
|
||||||
|
|
||||||
Install PKC via the PlexKodiConnect Kodi repository download button just below (do NOT use the standard GitHub download!). See the [github wiki installation manual](https://github.com/croneter/PlexKodiConnect/wiki/Installation) for a detailed guide. Please use the stable version except if you really know what you're doing. Kodi will update PKC automatically.
|
|
||||||
|
|
||||||
| Stable version | Beta version |
|
|
||||||
|----------------|--------------|
|
|
||||||
| [![stable version](https://img.shields.io/badge/stable_version-latest-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip) | [![beta version](https://img.shields.io/badge/beta_version-latest-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip) |
|
|
||||||
|
|
||||||
### Additional Artwork
|
### Additional Artwork
|
||||||
PKC uses additional artwork for free from [TheMovieDB](https://www.themoviedb.org). Many thanks for lettings us use the API, guys!
|
PKC uses additional artwork for free from [TheMovieDB](https://www.themoviedb.org). Many thanks for lettings us use the API, guys!
|
||||||
[![Logo of TheMovieDB](themoviedb.png)](https://www.themoviedb.org)
|
[![Logo of TheMovieDB](themoviedb.png)](https://www.themoviedb.org)
|
||||||
|
|
||||||
### Important Notes
|
|
||||||
|
|
||||||
1. If you are using a **low CPU device like a Raspberry Pi or a CuBox**, PKC might be instable or crash during initial sync. Lower the number of threads in the [PKC settings under Sync Options](https://github.com/croneter/PlexKodiConnect/wiki/PKC-settings#sync-options). Don't forget to reboot Kodi after that.
|
|
||||||
2. **Compatibility**:
|
|
||||||
* PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing.
|
|
||||||
* PKC is not (and will never be) compatible with the **MySQL database replacement** in Kodi. In fact, PKC replaces the MySQL functionality because it acts as a "man in the middle" for your entire media library.
|
|
||||||
* If **another plugin is not working** like it's supposed to, try to use [PKC direct paths](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Paths-Explained)
|
|
||||||
|
|
||||||
### Donations
|
### Donations
|
||||||
I'm not in any way affiliated with Plex. Thank you very much for a small donation via ko-fi.com and PayPal, Bitcoin or Ether if you appreciate PKC.
|
I'm not in any way affiliated with Plex. Thank you very much for a small donation via ko-fi.com and PayPal, Bitcoin or Ether if you appreciate PKC.
|
||||||
**Full disclaimer:** I will see your name and address if you use PayPal. Rest assured that I will not share this with anyone.
|
**Full disclaimer:** I will see your name and address if you use PayPal. Rest assured that I will not share this with anyone.
|
||||||
|
@ -116,18 +102,7 @@ I'm not in any way affiliated with Plex. Thank you very much for a small donatio
|
||||||
|
|
||||||
[![Feature Requests](http://feathub.com/croneter/PlexKodiConnect?format=svg)](http://feathub.com/croneter/PlexKodiConnect)
|
[![Feature Requests](http://feathub.com/croneter/PlexKodiConnect?format=svg)](http://feathub.com/croneter/PlexKodiConnect)
|
||||||
|
|
||||||
### Known Larger Issues
|
### Issues and Bugs
|
||||||
|
|
||||||
Solutions are unlikely due to the nature of these issues
|
|
||||||
- A Plex Media Server "bug" leads to frequent and slow syncs, see [here for more info](https://github.com/croneter/PlexKodiConnect/issues/135)
|
|
||||||
- *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 issues](https://github.com/croneter/PlexKodiConnect/issues/14) for more details. **Workaround**: use [PKC direct paths](https://github.com/croneter/PlexKodiConnect/wiki/Set-up-Direct-Paths) instead of addon 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 only on full/delta syncs (standard settings is every 60 minutes)
|
|
||||||
- Toggle the viewstate of an item to (un)watched outside of Kodi
|
|
||||||
|
|
||||||
|
|
||||||
### Issues being worked on
|
|
||||||
|
|
||||||
Have a look at the [Github Issues Page](https://github.com/croneter/PlexKodiConnect/issues). Before you open your own issue, please read [How to report a bug](https://github.com/croneter/PlexKodiConnect/wiki/How-to-Report-A-Bug).
|
Have a look at the [Github Issues Page](https://github.com/croneter/PlexKodiConnect/issues). Before you open your own issue, please read [How to report a bug](https://github.com/croneter/PlexKodiConnect/wiki/How-to-Report-A-Bug).
|
||||||
|
|
||||||
|
|
41
addon.xml
41
addon.xml
|
@ -1,10 +1,10 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.0.16" provider-name="croneter">
|
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.0.18" provider-name="croneter">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.1.0"/>
|
<import addon="xbmc.python" version="2.1.0"/>
|
||||||
<import addon="script.module.requests" version="2.9.1" />
|
<import addon="script.module.requests" version="2.9.1" />
|
||||||
<import addon="plugin.video.plexkodiconnect.movies" version="2.0.0" />
|
<import addon="plugin.video.plexkodiconnect.movies" version="2.0.1" />
|
||||||
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.1" />
|
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.2" />
|
||||||
</requires>
|
</requires>
|
||||||
<extension point="xbmc.python.pluginsource" library="default.py">
|
<extension point="xbmc.python.pluginsource" library="default.py">
|
||||||
<provides>video audio image</provides>
|
<provides>video audio image</provides>
|
||||||
|
@ -25,9 +25,6 @@
|
||||||
<website>https://github.com/croneter/PlexKodiConnect</website>
|
<website>https://github.com/croneter/PlexKodiConnect</website>
|
||||||
<email></email>
|
<email></email>
|
||||||
<source>https://github.com/croneter/PlexKodiConnect</source>
|
<source>https://github.com/croneter/PlexKodiConnect</source>
|
||||||
<summary lang="en_GB">Native Integration of Plex into Kodi</summary>
|
|
||||||
<description lang="en_GB">Connect Kodi to your Plex Media Server. This plugin assumes that you manage all your videos with Plex (and none with Kodi). You might lose data already stored in the Kodi video and music databases (as this plugin directly changes them). Use at your own risk!</description>
|
|
||||||
<disclaimer lang="en_GB">Use at your own risk</disclaimer>
|
|
||||||
<summary lang="nl_NL">Directe integratie van Plex in Kodi</summary>
|
<summary lang="nl_NL">Directe integratie van Plex in Kodi</summary>
|
||||||
<description lang="nl_NL">Verbind Kodi met je Plex Media Server. Deze plugin gaat ervan uit dat je al je video's met Plex (en niet met Kodi) beheerd. Je kunt gegevens reeds opgeslagen in de databases voor video en muziek van Kodi (deze plugin wijzigt deze gegevens direct) verliezen. Gebruik op eigen risico!</description>
|
<description lang="nl_NL">Verbind Kodi met je Plex Media Server. Deze plugin gaat ervan uit dat je al je video's met Plex (en niet met Kodi) beheerd. Je kunt gegevens reeds opgeslagen in de databases voor video en muziek van Kodi (deze plugin wijzigt deze gegevens direct) verliezen. Gebruik op eigen risico!</description>
|
||||||
<disclaimer lang="nl_NL">Gebruik op eigen risico</disclaimer>
|
<disclaimer lang="nl_NL">Gebruik op eigen risico</disclaimer>
|
||||||
|
@ -38,8 +35,8 @@
|
||||||
<description lang="fr_FR">Connecter Kodi à votre Plex Media Server. Ce plugin assume que vous souhaitez gérer toutes vos vidéos avec Plex (et aucune avec Kodi). Vous pourriez perdre les données déjà stockées dans les bases de données vidéo et musique de Kodi (ce plugin les modifie directement). Utilisez à vos propres risques !</description>
|
<description lang="fr_FR">Connecter Kodi à votre Plex Media Server. Ce plugin assume que vous souhaitez gérer toutes vos vidéos avec Plex (et aucune avec Kodi). Vous pourriez perdre les données déjà stockées dans les bases de données vidéo et musique de Kodi (ce plugin les modifie directement). Utilisez à vos propres risques !</description>
|
||||||
<disclaimer lang="fr_FR">A utiliser à vos propres risques</disclaimer>
|
<disclaimer lang="fr_FR">A utiliser à vos propres risques</disclaimer>
|
||||||
<summary lang="de_DE">Komplette Integration von Plex in Kodi</summary>
|
<summary lang="de_DE">Komplette Integration von Plex in Kodi</summary>
|
||||||
<description lang="de_DE">Verbindet Kodi mit deinem Plex Media Server. Dieses Addon geht davon aus, dass du all deine Videos mit Plex verwaltest (und keine direkt mit Kodi). Du wirst möglicherweise Daten verlieren, die bereits in der Kodi Video- und/oder Musik-Datenbank gespeichert sind (da dieses Addon beide Datenbanken direkt verändert). Verwendung auf eigene Gefahr!</description>
|
<description lang="de_DE">Verbindet Kodi mit deinem Plex Media Server. Dieses Addon geht davon aus, dass du all deine Videos mit Plex verwaltest (und keine direkt mit Kodi). Du wirst möglicherweise Daten verlieren, die bereits in der Kodi Video- und/oder Musik-Datenbank gespeichert sind (da dieses Addon beide Datenbanken direkt verändert). Benutzung auf eigene Gefahr!</description>
|
||||||
<disclaimer lang="de_DE">Verwendung auf eigene Gefahr</disclaimer>
|
<disclaimer lang="de_DE">Benutzung auf eigene Gefahr</disclaimer>
|
||||||
<summary lang="pt_PT">Integração nativa do Plex no Kodi</summary>
|
<summary lang="pt_PT">Integração nativa do Plex no Kodi</summary>
|
||||||
<description lang="pt_PT">Conectar o Kodi ao Servidor Plex Media. Este plugin assume que gerirá todos os vídeos com o Plex (e nenhum com Kodi). Poderá perder dados guardados nas bases de dados de vídeo e musica do Kodi (pois este plugin interfere diretamente com as mesmas). Use por risco de conta própria</description>
|
<description lang="pt_PT">Conectar o Kodi ao Servidor Plex Media. Este plugin assume que gerirá todos os vídeos com o Plex (e nenhum com Kodi). Poderá perder dados guardados nas bases de dados de vídeo e musica do Kodi (pois este plugin interfere diretamente com as mesmas). Use por risco de conta própria</description>
|
||||||
<disclaimer lang="pt_PT">Use por risco de conta própria</disclaimer>
|
<disclaimer lang="pt_PT">Use por risco de conta própria</disclaimer>
|
||||||
|
@ -61,7 +58,33 @@
|
||||||
<summary lang="da_DK">Indbygget Integration af Plex i Kodi</summary>
|
<summary lang="da_DK">Indbygget Integration af Plex i Kodi</summary>
|
||||||
<description lang="da_DK">Tilslut Kodi til din Plex Media Server. Dette plugin forudsætter, at du administrere alle dine videoer med Plex (og ikke med Kodi). Du kan miste data som allerede er gemt i Kodi video og musik-databaser (dette plugin ændrer direkte i dem). Brug på eget ansvar!</description>
|
<description lang="da_DK">Tilslut Kodi til din Plex Media Server. Dette plugin forudsætter, at du administrere alle dine videoer med Plex (og ikke med Kodi). Du kan miste data som allerede er gemt i Kodi video og musik-databaser (dette plugin ændrer direkte i dem). Brug på eget ansvar!</description>
|
||||||
<disclaimer lang="da_DK">Brug på eget ansvar</disclaimer>
|
<disclaimer lang="da_DK">Brug på eget ansvar</disclaimer>
|
||||||
<news>version 2.0.16 (beta only):
|
<summary lang="no_NO">Naturlig integrasjon av Plex til Kodi</summary>
|
||||||
|
<description lang="no_NO">Koble Kodi til din Plex Media Server. Denne plugin forventer at du organiserer alle dine videor med Plex (og ingen med Kodi). Du kan miste all data allerede lagret i Kodi video- og musikkdatabasene (da denne plugin umiddelbart forandrer dem). Bruk på egen risiko!</description>
|
||||||
|
<disclaimer lang="no_NO">Bruk på eget ansvar</disclaimer>
|
||||||
|
<summary lang="hu_HU">a Plex natív integrációja a Kodi-ba</summary>
|
||||||
|
<description lang="hu_HU">Csatlakoztassa a Kodi-t a Plex médiaszerveréhez. Ez a kiegészítő feltételezi, hogy az összes videóját a Plex-szel kezeli (és egyiket sem a Kodi-val). Elveszítheti a már a Kodi videó- és zene-adatbázisában tárolt adatokat (mivel ez a kiegészítő közvetlenül módosítja az adatbázisokat). Csak saját felelősségére használja!</description>
|
||||||
|
<disclaimer lang="hu_HU">Csak saját felelősségre használja</disclaimer>
|
||||||
|
<summary lang="ru_RU">Нативная интеграция сервера Plex в Kodi</summary>
|
||||||
|
<description lang="ru_RU">Подключите Kodi к своему серверу Plex. Плагин предполагает что вы управляете своими видео с помощью Plex (а не в Kodi). Вы можете потерять текущие базы данных музыки и видео в Kodi (так как плагин напрямую их изменяет). Используйте на свой страх и риск</description>
|
||||||
|
<disclaimer lang="ru_RU">Используйте на свой страх и риск</disclaimer>
|
||||||
|
<news>version 2.0.18 (beta only):
|
||||||
|
- Fix some playqueue inconsistencies using Plex Companion
|
||||||
|
- Direct paths: fix replaying item where playback was started via PMS
|
||||||
|
- Fix Plex trailers screwing up playqueue
|
||||||
|
- Fix IndexError when emptying Kodi playqueue
|
||||||
|
- Incorporate PKC player in kodimonitor module
|
||||||
|
- Fix pretty printing of PKC playqueues not working
|
||||||
|
- Code cleanups
|
||||||
|
|
||||||
|
version 2.0.17 (beta only):
|
||||||
|
- Finally make PKC compatible with Kodi 18 Leia Alpha 1
|
||||||
|
- Fix information screen and Plex option not working
|
||||||
|
- Activate Kodi background updates to hide "Compressing Database"
|
||||||
|
- Update translations
|
||||||
|
- Remove most strings not being used by PKC
|
||||||
|
- Remove some legacy settings
|
||||||
|
|
||||||
|
version 2.0.16 (beta only):
|
||||||
- Do NOT delete playstates before getting new ones from the PMS
|
- Do NOT delete playstates before getting new ones from the PMS
|
||||||
|
|
||||||
version 2.0.15 (beta only):
|
version 2.0.15 (beta only):
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
version 2.0.18 (beta only):
|
||||||
|
- Fix some playqueue inconsistencies using Plex Companion
|
||||||
|
- Direct paths: fix replaying item where playback was started via PMS
|
||||||
|
- Fix Plex trailers screwing up playqueue
|
||||||
|
- Fix IndexError when emptying Kodi playqueue
|
||||||
|
- Incorporate PKC player in kodimonitor module
|
||||||
|
- Fix pretty printing of PKC playqueues not working
|
||||||
|
- Code cleanups
|
||||||
|
|
||||||
|
version 2.0.17 (beta only):
|
||||||
|
- Finally make PKC compatible with Kodi 18 Leia Alpha 1
|
||||||
|
- Fix information screen and Plex option not working
|
||||||
|
- Activate Kodi background updates to hide "Compressing Database"
|
||||||
|
- Update translations
|
||||||
|
- Remove most strings not being used by PKC
|
||||||
|
- Remove some legacy settings
|
||||||
|
|
||||||
version 2.0.16 (beta only):
|
version 2.0.16 (beta only):
|
||||||
- Do NOT delete playstates before getting new ones from the PMS
|
- Do NOT delete playstates before getting new ones from the PMS
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ from Queue import Empty
|
||||||
from socket import SHUT_RDWR
|
from socket import SHUT_RDWR
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
|
|
||||||
from xbmc import sleep, executebuiltin
|
from xbmc import sleep, executebuiltin, Player
|
||||||
|
|
||||||
from utils import settings, thread_methods, language as lang, dialog
|
from utils import settings, thread_methods, language as lang, dialog
|
||||||
from plexbmchelper import listener, plexgdm, subscribers, httppersist
|
from plexbmchelper import listener, plexgdm, subscribers, httppersist
|
||||||
|
@ -18,7 +18,6 @@ from playlist_func import get_pms_playqueue, get_plextype_from_xml, \
|
||||||
get_playlist_details_from_xml
|
get_playlist_details_from_xml
|
||||||
from playback import playback_triage, play_xml
|
from playback import playback_triage, play_xml
|
||||||
import json_rpc as js
|
import json_rpc as js
|
||||||
import player
|
|
||||||
import variables as v
|
import variables as v
|
||||||
import state
|
import state
|
||||||
import playqueue as PQ
|
import playqueue as PQ
|
||||||
|
@ -43,7 +42,7 @@ class PlexCompanion(Thread):
|
||||||
self.client.clientDetails()
|
self.client.clientDetails()
|
||||||
LOG.debug("Registration string is:\n%s", self.client.getClientDetails())
|
LOG.debug("Registration string is:\n%s", self.client.getClientDetails())
|
||||||
# kodi player instance
|
# kodi player instance
|
||||||
self.player = player.PKC_Player()
|
self.player = Player()
|
||||||
self.httpd = False
|
self.httpd = False
|
||||||
self.subscription_manager = None
|
self.subscription_manager = None
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
|
|
|
@ -636,7 +636,7 @@ def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie',
|
||||||
xml[0].tag
|
xml[0].tag
|
||||||
except (IndexError, TypeError, AttributeError):
|
except (IndexError, TypeError, AttributeError):
|
||||||
LOG.error("Error retrieving metadata for %s", url)
|
LOG.error("Error retrieving metadata for %s", url)
|
||||||
return None
|
return
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
from xbmc import executebuiltin, translatePath
|
from xbmc import executebuiltin, translatePath
|
||||||
|
|
||||||
from utils import settings, window, language as lang, try_encode, try_decode, \
|
from utils import settings, window, language as lang, try_decode, dialog, \
|
||||||
XmlKodiSetting, reboot_kodi, dialog
|
XmlKodiSetting, reboot_kodi
|
||||||
from migration import check_migration
|
from migration import check_migration
|
||||||
from downloadutils import DownloadUtils as DU
|
from downloadutils import DownloadUtils as DU
|
||||||
from userclient import UserClient
|
from userclient import UserClient
|
||||||
|
@ -28,13 +28,13 @@ LOG = getLogger("PLEX." + __name__)
|
||||||
|
|
||||||
|
|
||||||
WINDOW_PROPERTIES = (
|
WINDOW_PROPERTIES = (
|
||||||
"plex_online", "plex_serverStatus", "plex_onWake", "plex_kodiScan",
|
"plex_online", "plex_serverStatus", "plex_shouldStop", "plex_dbScan",
|
||||||
"plex_shouldStop", "plex_dbScan", "plex_initialScan",
|
"plex_initialScan", "plex_customplayqueue", "plex_playbackProps",
|
||||||
"plex_customplayqueue", "plex_playbackProps", "pms_token", "plex_token",
|
"pms_token", "plex_token", "pms_server", "plex_machineIdentifier",
|
||||||
"pms_server", "plex_machineIdentifier", "plex_servername",
|
"plex_servername", "plex_authenticated", "PlexUserImage", "useDirectPaths",
|
||||||
"plex_authenticated", "PlexUserImage", "useDirectPaths", "countError",
|
"countError", "countUnauthorized", "plex_restricteduser",
|
||||||
"countUnauthorized", "plex_restricteduser", "plex_allows_mediaDeletion",
|
"plex_allows_mediaDeletion", "plex_command", "plex_result",
|
||||||
"plex_command", "plex_result", "plex_force_transcode_pix"
|
"plex_force_transcode_pix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -999,7 +999,7 @@ class TVShows(Items):
|
||||||
"""
|
"""
|
||||||
plex_dbitem = self.plex_db.getItem_byId(plex_id)
|
plex_dbitem = self.plex_db.getItem_byId(plex_id)
|
||||||
if plex_dbitem is None:
|
if plex_dbitem is None:
|
||||||
LOG.info('Cannot delete plex_id %s - not found in DB', plex_id)
|
LOG.debug('Cannot delete plex_id %s - not found in DB', plex_id)
|
||||||
return
|
return
|
||||||
kodi_id = plex_dbitem[0]
|
kodi_id = plex_dbitem[0]
|
||||||
file_id = plex_dbitem[1]
|
file_id = plex_dbitem[1]
|
||||||
|
@ -1075,7 +1075,7 @@ class TVShows(Items):
|
||||||
if v.KODIVERSION >= 17:
|
if v.KODIVERSION >= 17:
|
||||||
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
|
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
|
||||||
self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_SHOW)
|
self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_SHOW)
|
||||||
LOG.info("Removed tvshow: %s", kodi_id)
|
LOG.debug("Removed tvshow: %s", kodi_id)
|
||||||
|
|
||||||
def remove_season(self, kodi_id):
|
def remove_season(self, kodi_id):
|
||||||
"""
|
"""
|
||||||
|
@ -1086,7 +1086,7 @@ class TVShows(Items):
|
||||||
self.kodicursor)
|
self.kodicursor)
|
||||||
self.kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?",
|
self.kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?",
|
||||||
(kodi_id,))
|
(kodi_id,))
|
||||||
LOG.info("Removed season: %s", kodi_id)
|
LOG.debug("Removed season: %s", kodi_id)
|
||||||
|
|
||||||
def remove_episode(self, kodi_id, file_id):
|
def remove_episode(self, kodi_id, file_id):
|
||||||
"""
|
"""
|
||||||
|
@ -1102,7 +1102,7 @@ class TVShows(Items):
|
||||||
if v.KODIVERSION >= 17:
|
if v.KODIVERSION >= 17:
|
||||||
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE)
|
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE)
|
||||||
self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_EPISODE)
|
self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_EPISODE)
|
||||||
LOG.info("Removed episode: %s", kodi_id)
|
LOG.debug("Removed episode: %s", kodi_id)
|
||||||
|
|
||||||
|
|
||||||
class Music(Items):
|
class Music(Items):
|
||||||
|
@ -1719,109 +1719,101 @@ class Music(Items):
|
||||||
# Update album artwork
|
# Update album artwork
|
||||||
artwork.modify_artwork(artworks, albumid, v.KODI_TYPE_ALBUM, kodicursor)
|
artwork.modify_artwork(artworks, albumid, v.KODI_TYPE_ALBUM, kodicursor)
|
||||||
|
|
||||||
def remove(self, itemid):
|
def remove(self, plex_id):
|
||||||
"""
|
"""
|
||||||
Remove kodiid, file_id, pathid, plex reference
|
Completely remove the item with plex_id from the Kodi and Plex DBs.
|
||||||
|
Orphaned entries will also be deleted.
|
||||||
"""
|
"""
|
||||||
plex_db = self.plex_db
|
plex_dbitem = self.plex_db.getItem_byId(plex_id)
|
||||||
|
|
||||||
plex_dbitem = plex_db.getItem_byId(itemid)
|
|
||||||
try:
|
try:
|
||||||
kodiid = plex_dbitem[0]
|
kodi_id = plex_dbitem[0]
|
||||||
mediatype = plex_dbitem[4]
|
file_id = plex_dbitem[1]
|
||||||
LOG.info("Removing %s kodiid: %s", mediatype, kodiid)
|
parent_id = plex_dbitem[3]
|
||||||
|
kodi_type = plex_dbitem[4]
|
||||||
|
LOG.info("Removing %s with kodi_id: %s, parent_id: %s, file_id: %s",
|
||||||
|
kodi_type, kodi_id, parent_id, file_id)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
LOG.debug('Cannot delete item with plex id %s from Kodi', plex_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
##### PROCESS ITEM #####
|
|
||||||
|
|
||||||
# Remove the plex reference
|
# Remove the plex reference
|
||||||
plex_db.removeItem(itemid)
|
self.plex_db.removeItem(plex_id)
|
||||||
|
##### SONG #####
|
||||||
##### IF SONG #####
|
if kodi_type == v.KODI_TYPE_SONG:
|
||||||
|
|
||||||
if mediatype == v.KODI_TYPE_SONG:
|
|
||||||
# Delete song
|
# Delete song
|
||||||
self.removeSong(kodiid)
|
self.remove_song(kodi_id)
|
||||||
# This should only address single song scenario, where server doesn't actually
|
# Album verification
|
||||||
# create an album for the song.
|
|
||||||
plex_db.removeWildItem(itemid)
|
|
||||||
|
|
||||||
for item in plex_db.getItem_byWildId(itemid):
|
for item in self.plex_db.getItem_byWildId(plex_id):
|
||||||
|
|
||||||
item_kid = item[0]
|
item_kid = item[0]
|
||||||
item_mediatype = item[1]
|
item_kodi_type = item[1]
|
||||||
|
|
||||||
if item_mediatype == v.KODI_TYPE_ALBUM:
|
if item_kodi_type == v.KODI_TYPE_ALBUM:
|
||||||
childs = plex_db.getItem_byParentId(item_kid,
|
childs = self.plex_db.getItem_byParentId(item_kid,
|
||||||
v.KODI_TYPE_SONG)
|
v.KODI_TYPE_SONG)
|
||||||
if not childs:
|
if not childs:
|
||||||
# Delete album
|
# Delete album
|
||||||
self.removeAlbum(item_kid)
|
self.remove_album(item_kid)
|
||||||
|
|
||||||
##### IF ALBUM #####
|
##### ALBUM #####
|
||||||
elif mediatype == v.KODI_TYPE_ALBUM:
|
elif kodi_type == v.KODI_TYPE_ALBUM:
|
||||||
# Delete songs, album
|
# Delete songs, album
|
||||||
album_songs = plex_db.getItem_byParentId(kodiid,
|
album_songs = self.plex_db.getItem_byParentId(kodi_id,
|
||||||
v.KODI_TYPE_SONG)
|
v.KODI_TYPE_SONG)
|
||||||
for song in album_songs:
|
for song in album_songs:
|
||||||
self.removeSong(song[1])
|
self.remove_song(song[1])
|
||||||
# Remove plex songs
|
# Remove plex songs
|
||||||
plex_db.removeItems_byParentId(kodiid,
|
self.plex_db.removeItems_byParentId(kodi_id,
|
||||||
v.KODI_TYPE_SONG)
|
v.KODI_TYPE_SONG)
|
||||||
# Remove the album
|
# Remove the album
|
||||||
self.removeAlbum(kodiid)
|
self.remove_album(kodi_id)
|
||||||
|
|
||||||
##### IF ARTIST #####
|
##### IF ARTIST #####
|
||||||
elif mediatype == v.KODI_TYPE_ARTIST:
|
elif kodi_type == v.KODI_TYPE_ARTIST:
|
||||||
# Delete songs, album, artist
|
# Delete songs, album, artist
|
||||||
albums = plex_db.getItem_byParentId(kodiid,
|
albums = self.plex_db.getItem_byParentId(kodi_id, v.KODI_TYPE_ALBUM)
|
||||||
v.KODI_TYPE_ALBUM)
|
|
||||||
for album in albums:
|
for album in albums:
|
||||||
albumid = album[1]
|
albumid = album[1]
|
||||||
album_songs = plex_db.getItem_byParentId(albumid,
|
album_songs = self.plex_db.getItem_byParentId(albumid,
|
||||||
v.KODI_TYPE_SONG)
|
v.KODI_TYPE_SONG)
|
||||||
for song in album_songs:
|
for song in album_songs:
|
||||||
self.removeSong(song[1])
|
self.remove_song(song[1])
|
||||||
# Remove plex song
|
# Remove plex song
|
||||||
plex_db.removeItems_byParentId(albumid,
|
self.plex_db.removeItems_byParentId(albumid, v.KODI_TYPE_SONG)
|
||||||
v.KODI_TYPE_SONG)
|
|
||||||
# Remove plex artist
|
# Remove plex artist
|
||||||
plex_db.removeItems_byParentId(albumid,
|
self.plex_db.removeItems_byParentId(albumid, v.KODI_TYPE_ARTIST)
|
||||||
v.KODI_TYPE_ARTIST)
|
|
||||||
# Remove kodi album
|
# Remove kodi album
|
||||||
self.removeAlbum(albumid)
|
self.remove_album(albumid)
|
||||||
# Remove plex albums
|
# Remove plex albums
|
||||||
plex_db.removeItems_byParentId(kodiid,
|
self.plex_db.removeItems_byParentId(kodi_id, v.KODI_TYPE_ALBUM)
|
||||||
v.KODI_TYPE_ALBUM)
|
|
||||||
|
|
||||||
# Remove artist
|
# Remove artist
|
||||||
self.removeArtist(kodiid)
|
self.remove_artist(kodi_id)
|
||||||
|
|
||||||
LOG.info("Deleted %s: %s from kodi database", mediatype, itemid)
|
LOG.debug("Deleted plex_id %s from kodi database", plex_id)
|
||||||
|
|
||||||
def removeSong(self, kodiid):
|
def remove_song(self, kodi_id):
|
||||||
"""
|
"""
|
||||||
Remove song, and only the song
|
Remove song, and only the song
|
||||||
"""
|
"""
|
||||||
self.artwork.delete_artwork(kodiid, v.KODI_TYPE_SONG, self.kodicursor)
|
self.artwork.delete_artwork(kodi_id, v.KODI_TYPE_SONG, self.kodicursor)
|
||||||
self.kodicursor.execute("DELETE FROM song WHERE idSong = ?",
|
self.kodicursor.execute("DELETE FROM song WHERE idSong = ?",
|
||||||
(kodiid,))
|
(kodi_id,))
|
||||||
|
|
||||||
def removeAlbum(self, kodiid):
|
def remove_album(self, kodi_id):
|
||||||
"""
|
"""
|
||||||
Remove an album, and only the album
|
Remove an album, and only the album
|
||||||
"""
|
"""
|
||||||
self.artwork.delete_artwork(kodiid, v.KODI_TYPE_ALBUM, self.kodicursor)
|
self.artwork.delete_artwork(kodi_id, v.KODI_TYPE_ALBUM, self.kodicursor)
|
||||||
self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?",
|
self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?",
|
||||||
(kodiid,))
|
(kodi_id,))
|
||||||
|
|
||||||
def removeArtist(self, kodiid):
|
def remove_artist(self, kodi_id):
|
||||||
"""
|
"""
|
||||||
Remove an artist, and only the artist
|
Remove an artist, and only the artist
|
||||||
"""
|
"""
|
||||||
self.artwork.delete_artwork(kodiid,
|
self.artwork.delete_artwork(kodi_id,
|
||||||
v.KODI_TYPE_ARTIST,
|
v.KODI_TYPE_ARTIST,
|
||||||
self.kodicursor)
|
self.kodicursor)
|
||||||
self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?",
|
self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?",
|
||||||
(kodiid,))
|
(kodi_id,))
|
||||||
|
|
|
@ -204,24 +204,20 @@ class KodiDBMethods(object):
|
||||||
self.cursor.execute(query, (file_id, path_id, filename, date_added))
|
self.cursor.execute(query, (file_id, path_id, filename, date_added))
|
||||||
return file_id
|
return file_id
|
||||||
|
|
||||||
def clean_file_table(self):
|
def obsolete_file_ids(self):
|
||||||
"""
|
"""
|
||||||
Hack: using Direct Paths, Kodi adds all addon paths to the files table
|
Returns a list of (idFile,) tuples (ints) of all Kodi file ids that do
|
||||||
but without a dateAdded entry. This method cleans up all file entries
|
not have a dateAdded set (dateAdded is NULL) and the filename start with
|
||||||
without a dateAdded entry - to be called after playback has ended.
|
'plugin://plugin.video.plexkodiconnect'
|
||||||
|
These entries should be deleted as they're created falsely by Kodi.
|
||||||
"""
|
"""
|
||||||
self.cursor.execute('SELECT idFile FROM files WHERE dateAdded IS NULL')
|
query = '''
|
||||||
files = self.cursor.fetchall()
|
SELECT idFile FROM files
|
||||||
self.cursor.execute('DELETE FROM files where dateAdded IS NULL')
|
WHERE dateAdded IS NULL
|
||||||
for file in files:
|
AND strFilename LIKE \'plugin://plugin.video.plexkodiconnect%\'
|
||||||
self.cursor.execute('DELETE FROM bookmark WHERE idFile = ?',
|
'''
|
||||||
(file[0],))
|
self.cursor.execute(query)
|
||||||
self.cursor.execute('DELETE FROM settings WHERE idFile = ?',
|
return self.cursor.fetchall()
|
||||||
(file[0],))
|
|
||||||
self.cursor.execute('DELETE FROM streamdetails WHERE idFile = ?',
|
|
||||||
(file[0],))
|
|
||||||
self.cursor.execute('DELETE FROM stacktimes WHERE idFile = ?',
|
|
||||||
(file[0],))
|
|
||||||
|
|
||||||
def show_id_from_path(self, path):
|
def show_id_from_path(self, path):
|
||||||
"""
|
"""
|
||||||
|
@ -241,10 +237,12 @@ class KodiDBMethods(object):
|
||||||
show_id = None
|
show_id = None
|
||||||
return show_id
|
return show_id
|
||||||
|
|
||||||
def remove_file(self, file_id):
|
def remove_file(self, file_id, remove_orphans=True):
|
||||||
"""
|
"""
|
||||||
Removes the entry for file_id from the files table. Will also delete
|
Removes the entry for file_id from the files table. Will also delete
|
||||||
entries from the associated tables: bookmark, settings, streamdetails
|
entries from the associated tables: bookmark, settings, streamdetails.
|
||||||
|
If remove_orphans is true, this method will delete any orphaned path
|
||||||
|
entries in the Kodi path table
|
||||||
"""
|
"""
|
||||||
self.cursor.execute('SELECT idPath FROM files WHERE idFile = ? LIMIT 1',
|
self.cursor.execute('SELECT idPath FROM files WHERE idFile = ? LIMIT 1',
|
||||||
(file_id,))
|
(file_id,))
|
||||||
|
@ -262,12 +260,13 @@ class KodiDBMethods(object):
|
||||||
(file_id,))
|
(file_id,))
|
||||||
self.cursor.execute('DELETE FROM stacktimes WHERE idFile = ?',
|
self.cursor.execute('DELETE FROM stacktimes WHERE idFile = ?',
|
||||||
(file_id,))
|
(file_id,))
|
||||||
# Delete orphaned path entry
|
if remove_orphans:
|
||||||
self.cursor.execute('SELECT idFile FROM files WHERE idPath = ? LIMIT 1',
|
# Delete orphaned path entry
|
||||||
(path_id,))
|
query = 'SELECT idFile FROM files WHERE idPath = ? LIMIT 1'
|
||||||
if self.cursor.fetchone() is None:
|
self.cursor.execute(query, (path_id,))
|
||||||
self.cursor.execute('DELETE FROM path WHERE idPath = ?',
|
if self.cursor.fetchone() is None:
|
||||||
(path_id,))
|
self.cursor.execute('DELETE FROM path WHERE idPath = ?',
|
||||||
|
(path_id,))
|
||||||
|
|
||||||
def _modify_link_and_table(self, kodi_id, kodi_type, entries, link_table,
|
def _modify_link_and_table(self, kodi_id, kodi_type, entries, link_table,
|
||||||
table, key):
|
table, key):
|
||||||
|
|
|
@ -6,13 +6,15 @@ from json import loads
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from xbmc import Monitor, Player, sleep, getCondVisibility, getInfoLabel, \
|
import xbmc
|
||||||
getLocalizedString
|
|
||||||
from xbmcgui import Window
|
from xbmcgui import Window
|
||||||
|
|
||||||
import plexdb_functions as plexdb
|
import plexdb_functions as plexdb
|
||||||
from utils import window, settings, plex_command, thread_methods, try_encode
|
import kodidb_functions as kodidb
|
||||||
|
from utils import window, settings, plex_command, thread_methods, try_encode, \
|
||||||
|
kodi_time_to_millis, unix_date_to_kodi, unix_timestamp
|
||||||
from PlexFunctions import scrobble
|
from PlexFunctions import scrobble
|
||||||
|
from downloadutils import DownloadUtils as DU
|
||||||
from kodidb_functions import kodiid_from_filename
|
from kodidb_functions import kodiid_from_filename
|
||||||
from plexbmchelper.subscribers import LOCKER
|
from plexbmchelper.subscribers import LOCKER
|
||||||
from playback import playback_triage
|
from playback import playback_triage
|
||||||
|
@ -21,6 +23,7 @@ import playqueue as PQ
|
||||||
import json_rpc as js
|
import json_rpc as js
|
||||||
import playlist_func as PL
|
import playlist_func as PL
|
||||||
import state
|
import state
|
||||||
|
import variables as v
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -53,14 +56,14 @@ STATE_SETTINGS = {
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
class KodiMonitor(Monitor):
|
class KodiMonitor(xbmc.Monitor):
|
||||||
"""
|
"""
|
||||||
PKC implementation of the Kodi Monitor class. Invoke only once.
|
PKC implementation of the Kodi Monitor class. Invoke only once.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.xbmcplayer = Player()
|
self.xbmcplayer = xbmc.Player()
|
||||||
self._already_slept = False
|
self._already_slept = False
|
||||||
Monitor.__init__(self)
|
xbmc.Monitor.__init__(self)
|
||||||
for playerid in state.PLAYER_STATES:
|
for playerid in state.PLAYER_STATES:
|
||||||
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
|
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
|
||||||
state.OLD_PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
|
state.OLD_PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
|
||||||
|
@ -71,16 +74,12 @@ class KodiMonitor(Monitor):
|
||||||
Will be called when Kodi starts scanning the library
|
Will be called when Kodi starts scanning the library
|
||||||
"""
|
"""
|
||||||
LOG.debug("Kodi library scan %s running.", library)
|
LOG.debug("Kodi library scan %s running.", library)
|
||||||
if library == "video":
|
|
||||||
window('plex_kodiScan', value="true")
|
|
||||||
|
|
||||||
def onScanFinished(self, library):
|
def onScanFinished(self, library):
|
||||||
"""
|
"""
|
||||||
Will be called when Kodi finished scanning the library
|
Will be called when Kodi finished scanning the library
|
||||||
"""
|
"""
|
||||||
LOG.debug("Kodi library scan %s finished.", library)
|
LOG.debug("Kodi library scan %s finished.", library)
|
||||||
if library == "video":
|
|
||||||
window('plex_kodiScan', clear=True)
|
|
||||||
|
|
||||||
def onSettingsChanged(self):
|
def onSettingsChanged(self):
|
||||||
"""
|
"""
|
||||||
|
@ -141,7 +140,15 @@ class KodiMonitor(Monitor):
|
||||||
elif method == "Player.OnStop":
|
elif method == "Player.OnStop":
|
||||||
# Should refresh our video nodes, e.g. on deck
|
# Should refresh our video nodes, e.g. on deck
|
||||||
# xbmc.executebuiltin('ReloadSkin()')
|
# xbmc.executebuiltin('ReloadSkin()')
|
||||||
pass
|
if data.get('end'):
|
||||||
|
if state.PKC_CAUSED_STOP is True:
|
||||||
|
state.PKC_CAUSED_STOP = False
|
||||||
|
state.PKC_CAUSED_STOP_DONE = True
|
||||||
|
LOG.debug('PKC caused this playback stop - ignoring')
|
||||||
|
else:
|
||||||
|
_playback_cleanup(ended=True)
|
||||||
|
else:
|
||||||
|
_playback_cleanup()
|
||||||
elif method == 'Playlist.OnAdd':
|
elif method == 'Playlist.OnAdd':
|
||||||
self._playlist_onadd(data)
|
self._playlist_onadd(data)
|
||||||
elif method == 'Playlist.OnRemove':
|
elif method == 'Playlist.OnRemove':
|
||||||
|
@ -169,17 +176,11 @@ class KodiMonitor(Monitor):
|
||||||
LOG.error("Could not find itemid in plex database for a "
|
LOG.error("Could not find itemid in plex database for a "
|
||||||
"video library update")
|
"video library update")
|
||||||
else:
|
else:
|
||||||
# Stop from manually marking as watched unwatched, with
|
# notify the server
|
||||||
# actual playback.
|
if playcount > 0:
|
||||||
if window('plex_skipWatched%s' % itemid) == "true":
|
scrobble(itemid, 'watched')
|
||||||
# property is set in player.py
|
|
||||||
window('plex_skipWatched%s' % itemid, clear=True)
|
|
||||||
else:
|
else:
|
||||||
# notify the server
|
scrobble(itemid, 'unwatched')
|
||||||
if playcount > 0:
|
|
||||||
scrobble(itemid, 'watched')
|
|
||||||
else:
|
|
||||||
scrobble(itemid, 'unwatched')
|
|
||||||
elif method == "VideoLibrary.OnRemove":
|
elif method == "VideoLibrary.OnRemove":
|
||||||
pass
|
pass
|
||||||
elif method == "System.OnSleep":
|
elif method == "System.OnSleep":
|
||||||
|
@ -188,12 +189,11 @@ class KodiMonitor(Monitor):
|
||||||
window('plex_online', value="sleep")
|
window('plex_online', value="sleep")
|
||||||
elif method == "System.OnWake":
|
elif method == "System.OnWake":
|
||||||
# Allow network to wake up
|
# Allow network to wake up
|
||||||
sleep(10000)
|
xbmc.sleep(10000)
|
||||||
window('plex_onWake', value="true")
|
|
||||||
window('plex_online', value="false")
|
window('plex_online', value="false")
|
||||||
elif method == "GUI.OnScreensaverDeactivated":
|
elif method == "GUI.OnScreensaverDeactivated":
|
||||||
if settings('dbSyncScreensaver') == "true":
|
if settings('dbSyncScreensaver') == "true":
|
||||||
sleep(5000)
|
xbmc.sleep(5000)
|
||||||
plex_command('RUN_LIB_SCAN', 'full')
|
plex_command('RUN_LIB_SCAN', 'full')
|
||||||
elif method == "System.OnQuit":
|
elif method == "System.OnQuit":
|
||||||
LOG.info('Kodi OnQuit detected - shutting down')
|
LOG.info('Kodi OnQuit detected - shutting down')
|
||||||
|
@ -254,6 +254,7 @@ class KodiMonitor(Monitor):
|
||||||
playqueue = PQ.PLAYQUEUES[data['playlistid']]
|
playqueue = PQ.PLAYQUEUES[data['playlistid']]
|
||||||
if not playqueue.is_pkc_clear():
|
if not playqueue.is_pkc_clear():
|
||||||
playqueue.clear(kodi=False)
|
playqueue.clear(kodi=False)
|
||||||
|
playqueue.pkc_edit = True
|
||||||
else:
|
else:
|
||||||
LOG.debug('Detected PKC clear - ignoring')
|
LOG.debug('Detected PKC clear - ignoring')
|
||||||
|
|
||||||
|
@ -307,7 +308,7 @@ class KodiMonitor(Monitor):
|
||||||
# start as Kodi updates this info very late!! Might get previous
|
# start as Kodi updates this info very late!! Might get previous
|
||||||
# element otherwise
|
# element otherwise
|
||||||
self._already_slept = True
|
self._already_slept = True
|
||||||
sleep(1000)
|
xbmc.sleep(1000)
|
||||||
json_item = js.get_item(playerid)
|
json_item = js.get_item(playerid)
|
||||||
LOG.debug('Kodi playing item properties: %s', json_item)
|
LOG.debug('Kodi playing item properties: %s', json_item)
|
||||||
return (json_item.get('id'),
|
return (json_item.get('id'),
|
||||||
|
@ -424,16 +425,130 @@ class SpecialMonitor(Thread):
|
||||||
def run(self):
|
def run(self):
|
||||||
LOG.info("----====# Starting Special Monitor #====----")
|
LOG.info("----====# Starting Special Monitor #====----")
|
||||||
# "Start from beginning", "Play from beginning"
|
# "Start from beginning", "Play from beginning"
|
||||||
strings = (try_encode(getLocalizedString(12021)),
|
strings = (try_encode(xbmc.getLocalizedString(12021)),
|
||||||
try_encode(getLocalizedString(12023)))
|
try_encode(xbmc.getLocalizedString(12023)))
|
||||||
while not self.stopped():
|
while not self.stopped():
|
||||||
if getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'):
|
if xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'):
|
||||||
if getInfoLabel('Control.GetLabel(1002)') in strings:
|
if xbmc.getInfoLabel('Control.GetLabel(1002)') in strings:
|
||||||
# Remember that the item IS indeed resumable
|
# Remember that the item IS indeed resumable
|
||||||
control = int(Window(10106).getFocusId())
|
control = int(Window(10106).getFocusId())
|
||||||
state.RESUME_PLAYBACK = True if control == 1001 else False
|
state.RESUME_PLAYBACK = True if control == 1001 else False
|
||||||
else:
|
else:
|
||||||
# Different context menu is displayed
|
# Different context menu is displayed
|
||||||
state.RESUME_PLAYBACK = False
|
state.RESUME_PLAYBACK = False
|
||||||
sleep(200)
|
xbmc.sleep(200)
|
||||||
LOG.info("#====---- Special Monitor Stopped ----====#")
|
LOG.info("#====---- Special Monitor Stopped ----====#")
|
||||||
|
|
||||||
|
|
||||||
|
@LOCKER.lockthis
|
||||||
|
def _playback_cleanup(ended=False):
|
||||||
|
"""
|
||||||
|
PKC cleanup after playback ends/is stopped. Pass ended=True if Kodi
|
||||||
|
completely finished playing an item (because we will get and use wrong
|
||||||
|
timing data otherwise)
|
||||||
|
"""
|
||||||
|
LOG.debug('playback_cleanup called. Active players: %s',
|
||||||
|
state.ACTIVE_PLAYERS)
|
||||||
|
# We might have saved a transient token from a user flinging media via
|
||||||
|
# Companion (if we could not use the playqueue to store the token)
|
||||||
|
state.PLEX_TRANSIENT_TOKEN = None
|
||||||
|
for playerid in state.ACTIVE_PLAYERS:
|
||||||
|
status = state.PLAYER_STATES[playerid]
|
||||||
|
# Remember the last played item later
|
||||||
|
state.OLD_PLAYER_STATES[playerid] = copy.deepcopy(status)
|
||||||
|
# Stop transcoding
|
||||||
|
if status['playmethod'] == 'Transcode':
|
||||||
|
LOG.debug('Tell the PMS to stop transcoding')
|
||||||
|
DU().downloadUrl(
|
||||||
|
'{server}/video/:/transcode/universal/stop',
|
||||||
|
parameters={'session': v.PKC_MACHINE_IDENTIFIER})
|
||||||
|
if playerid == 1:
|
||||||
|
# Bookmarks might not be pickup up correctly, so let's do them
|
||||||
|
# manually. Applies to addon paths, but direct paths might have
|
||||||
|
# started playback via PMS
|
||||||
|
_record_playstate(status, ended)
|
||||||
|
# Reset the player's status
|
||||||
|
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
|
||||||
|
# As all playback has halted, reset the players that have been active
|
||||||
|
state.ACTIVE_PLAYERS = []
|
||||||
|
LOG.debug('Finished PKC playback cleanup')
|
||||||
|
|
||||||
|
|
||||||
|
def _record_playstate(status, ended):
|
||||||
|
if not status['plex_id']:
|
||||||
|
LOG.debug('No Plex id found to record playstate for status %s', status)
|
||||||
|
return
|
||||||
|
with plexdb.Get_Plex_DB() as plex_db:
|
||||||
|
kodi_db_item = plex_db.getItem_byId(status['plex_id'])
|
||||||
|
if kodi_db_item is None:
|
||||||
|
# Item not (yet) in Kodi library
|
||||||
|
LOG.debug('No playstate update due to Plex id not found: %s', status)
|
||||||
|
return
|
||||||
|
totaltime = float(kodi_time_to_millis(status['totaltime'])) / 1000
|
||||||
|
if ended:
|
||||||
|
progress = 0.99
|
||||||
|
time = v.IGNORE_SECONDS_AT_START + 1
|
||||||
|
else:
|
||||||
|
time = float(kodi_time_to_millis(status['time'])) / 1000
|
||||||
|
try:
|
||||||
|
progress = time / totaltime
|
||||||
|
except ZeroDivisionError:
|
||||||
|
progress = 0.0
|
||||||
|
LOG.debug('Playback progress %s (%s of %s seconds)',
|
||||||
|
progress, time, totaltime)
|
||||||
|
playcount = status['playcount']
|
||||||
|
last_played = unix_date_to_kodi(unix_timestamp())
|
||||||
|
if playcount is None:
|
||||||
|
LOG.debug('playcount not found, looking it up in the Kodi DB')
|
||||||
|
with kodidb.GetKodiDB('video') as kodi_db:
|
||||||
|
playcount = kodi_db.get_playcount(kodi_db_item[1])
|
||||||
|
playcount = 0 if playcount is None else playcount
|
||||||
|
if time < v.IGNORE_SECONDS_AT_START:
|
||||||
|
LOG.debug('Ignoring playback less than %s seconds',
|
||||||
|
v.IGNORE_SECONDS_AT_START)
|
||||||
|
# Annoying Plex bug - it'll reset an already watched video to unwatched
|
||||||
|
playcount = None
|
||||||
|
last_played = None
|
||||||
|
time = 0
|
||||||
|
elif progress >= v.MARK_PLAYED_AT:
|
||||||
|
LOG.debug('Recording entirely played video since progress > %s',
|
||||||
|
v.MARK_PLAYED_AT)
|
||||||
|
playcount += 1
|
||||||
|
time = 0
|
||||||
|
with kodidb.GetKodiDB('video') as kodi_db:
|
||||||
|
kodi_db.addPlaystate(kodi_db_item[1],
|
||||||
|
time,
|
||||||
|
totaltime,
|
||||||
|
playcount,
|
||||||
|
last_played)
|
||||||
|
# Hack to force "in progress" widget to appear if it wasn't visible before
|
||||||
|
if (state.FORCE_RELOAD_SKIN and
|
||||||
|
xbmc.getCondVisibility('Window.IsVisible(Home.xml)')):
|
||||||
|
LOG.debug('Refreshing skin to update widgets')
|
||||||
|
xbmc.executebuiltin('ReloadSkin()')
|
||||||
|
thread = Thread(target=_clean_file_table)
|
||||||
|
thread.setDaemon(True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_file_table():
|
||||||
|
"""
|
||||||
|
If we associate a playing video e.g. pointing to plugin://... to an existing
|
||||||
|
Kodi library item, Kodi will add an additional entry for this (additional)
|
||||||
|
path plugin:// in the file table. This leads to all sorts of wierd behavior.
|
||||||
|
This function tries for at most 5 seconds to clean the file table.
|
||||||
|
"""
|
||||||
|
LOG.debug('Start cleaning Kodi files table')
|
||||||
|
i = 0
|
||||||
|
while i < 100 and not state.STOP_PKC:
|
||||||
|
with kodidb.GetKodiDB('video') as kodi_db:
|
||||||
|
files = kodi_db.obsolete_file_ids()
|
||||||
|
if files:
|
||||||
|
break
|
||||||
|
i += 1
|
||||||
|
xbmc.sleep(50)
|
||||||
|
with kodidb.GetKodiDB('video') as kodi_db:
|
||||||
|
for file_id in files:
|
||||||
|
LOG.debug('Removing obsolete Kodi file_id %s', file_id)
|
||||||
|
kodi_db.remove_file(file_id[0], remove_orphans=False)
|
||||||
|
LOG.debug('Done cleaning up Kodi file table')
|
||||||
|
|
|
@ -49,10 +49,11 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
|
||||||
the first pass - e.g. if you're calling this function from the original
|
the first pass - e.g. if you're calling this function from the original
|
||||||
service.py Python instance
|
service.py Python instance
|
||||||
"""
|
"""
|
||||||
LOG.info('playback_triage called with plex_id %s, plex_type %s, path %s',
|
LOG.info('playback_triage called with plex_id %s, plex_type %s, path %s, '
|
||||||
plex_id, plex_type, path)
|
'resolve %s,', plex_id, plex_type, path, resolve)
|
||||||
global RESOLVE
|
global RESOLVE
|
||||||
RESOLVE = resolve
|
# If started via Kodi context menu, we never resolve
|
||||||
|
RESOLVE = resolve if not state.CONTEXT_MENU_PLAY else False
|
||||||
if not state.AUTHENTICATED:
|
if not state.AUTHENTICATED:
|
||||||
LOG.error('Not yet authenticated for PMS, abort starting playback')
|
LOG.error('Not yet authenticated for PMS, abort starting playback')
|
||||||
# "Unauthorized for PMS"
|
# "Unauthorized for PMS"
|
||||||
|
@ -64,7 +65,7 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
|
||||||
pos = js.get_position(playqueue.playlistid)
|
pos = js.get_position(playqueue.playlistid)
|
||||||
# Can return -1 (as in "no playlist")
|
# Can return -1 (as in "no playlist")
|
||||||
pos = pos if pos != -1 else 0
|
pos = pos if pos != -1 else 0
|
||||||
LOG.debug('playQueue position: %s for %s', pos, playqueue)
|
LOG.debug('playQueue position %s for %s', pos, playqueue)
|
||||||
# Have we already initiated playback?
|
# Have we already initiated playback?
|
||||||
try:
|
try:
|
||||||
item = playqueue.items[pos]
|
item = playqueue.items[pos]
|
||||||
|
@ -98,7 +99,7 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
|
||||||
try:
|
try:
|
||||||
_init_existing_kodi_playlist(playqueue, pos)
|
_init_existing_kodi_playlist(playqueue, pos)
|
||||||
except PL.PlaylistError:
|
except PL.PlaylistError:
|
||||||
LOG.error('Aborting playback_init for longer Kodi playlist')
|
LOG.error('Playback_init for existing Kodi playlist failed')
|
||||||
_ensure_resolve(abort=True)
|
_ensure_resolve(abort=True)
|
||||||
return
|
return
|
||||||
# Now we need to use setResolvedUrl for the item at position ZERO
|
# Now we need to use setResolvedUrl for the item at position ZERO
|
||||||
|
@ -107,7 +108,8 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
|
||||||
return
|
return
|
||||||
# "Usual" case - consider trailers and parts and build both Kodi and Plex
|
# "Usual" case - consider trailers and parts and build both Kodi and Plex
|
||||||
# playqueues
|
# playqueues
|
||||||
# Fail the item we're trying to play now so we can restart the player
|
# Pass dummy PKC video with 0 length so Kodi immediately stops playback
|
||||||
|
# and we can build our own playqueue.
|
||||||
_ensure_resolve()
|
_ensure_resolve()
|
||||||
api = API(xml[0])
|
api = API(xml[0])
|
||||||
trailers = False
|
trailers = False
|
||||||
|
@ -120,6 +122,10 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
|
||||||
else:
|
else:
|
||||||
trailers = True
|
trailers = True
|
||||||
LOG.debug('Playing trailers: %s', trailers)
|
LOG.debug('Playing trailers: %s', trailers)
|
||||||
|
if RESOLVE:
|
||||||
|
# Sleep a bit to let setResolvedUrl do its thing - bit ugly
|
||||||
|
while not state.PKC_CAUSED_STOP_DONE:
|
||||||
|
sleep(50)
|
||||||
playqueue.clear()
|
playqueue.clear()
|
||||||
if plex_type != v.PLEX_TYPE_CLIP:
|
if plex_type != v.PLEX_TYPE_CLIP:
|
||||||
# Post to the PMS to create a playqueue - in any case due to Companion
|
# Post to the PMS to create a playqueue - in any case due to Companion
|
||||||
|
@ -132,13 +138,13 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
|
||||||
plex_id, xml.attrib.get('librarySectionUUID'))
|
plex_id, xml.attrib.get('librarySectionUUID'))
|
||||||
# "Play error"
|
# "Play error"
|
||||||
dialog('notification', lang(29999), lang(30128), icon='{error}')
|
dialog('notification', lang(29999), lang(30128), icon='{error}')
|
||||||
_ensure_resolve(abort=True)
|
# Do NOT use _ensure_resolve() because we resolved above already
|
||||||
|
state.CONTEXT_MENU_PLAY = False
|
||||||
|
state.FORCE_TRANSCODE = False
|
||||||
|
state.RESUME_PLAYBACK = False
|
||||||
return
|
return
|
||||||
# Should already be empty, but just in case
|
|
||||||
PL.get_playlist_details_from_xml(playqueue, xml)
|
PL.get_playlist_details_from_xml(playqueue, xml)
|
||||||
stack = _prep_playlist_stack(xml)
|
stack = _prep_playlist_stack(xml)
|
||||||
# Sleep a bit to let setResolvedUrl do its thing - bit ugly
|
|
||||||
sleep(200)
|
|
||||||
_process_stack(playqueue, stack)
|
_process_stack(playqueue, stack)
|
||||||
# Always resume if playback initiated via PMS and there IS a resume
|
# Always resume if playback initiated via PMS and there IS a resume
|
||||||
# point
|
# point
|
||||||
|
@ -146,7 +152,6 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
|
||||||
# Reset some playback variables
|
# Reset some playback variables
|
||||||
state.CONTEXT_MENU_PLAY = False
|
state.CONTEXT_MENU_PLAY = False
|
||||||
state.FORCE_TRANSCODE = False
|
state.FORCE_TRANSCODE = False
|
||||||
# Do NOT set offset, because Kodi player will return here to resolveURL
|
|
||||||
# New thread to release this one sooner (e.g. harddisk spinning up)
|
# New thread to release this one sooner (e.g. harddisk spinning up)
|
||||||
thread = Thread(target=threaded_playback,
|
thread = Thread(target=threaded_playback,
|
||||||
args=(playqueue.kodi_pl, pos, offset))
|
args=(playqueue.kodi_pl, pos, offset))
|
||||||
|
@ -160,6 +165,8 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
|
||||||
# plugin://pkc will be lost; Kodi will try to startup playback for an empty
|
# plugin://pkc will be lost; Kodi will try to startup playback for an empty
|
||||||
# path: log entry is "CGUIWindowVideoBase::OnPlayMedia <missing path>"
|
# path: log entry is "CGUIWindowVideoBase::OnPlayMedia <missing path>"
|
||||||
thread.start()
|
thread.start()
|
||||||
|
# Ensure that PKC playqueue monitor ignores the changes we just made
|
||||||
|
playqueue.pkc_edit = True
|
||||||
|
|
||||||
|
|
||||||
def _ensure_resolve(abort=False):
|
def _ensure_resolve(abort=False):
|
||||||
|
@ -173,9 +180,10 @@ def _ensure_resolve(abort=False):
|
||||||
"""
|
"""
|
||||||
if RESOLVE:
|
if RESOLVE:
|
||||||
LOG.debug('Passing dummy path to Kodi')
|
LOG.debug('Passing dummy path to Kodi')
|
||||||
if not state.CONTEXT_MENU_PLAY:
|
# if not state.CONTEXT_MENU_PLAY:
|
||||||
# Because playback won't start with context menu play
|
# Because playback won't start with context menu play
|
||||||
state.PKC_CAUSED_STOP = True
|
state.PKC_CAUSED_STOP = True
|
||||||
|
state.PKC_CAUSED_STOP_DONE = False
|
||||||
result = Playback_Successful()
|
result = Playback_Successful()
|
||||||
result.listitem = PKC_ListItem(path=NULL_VIDEO)
|
result.listitem = PKC_ListItem(path=NULL_VIDEO)
|
||||||
pickle_me(result)
|
pickle_me(result)
|
||||||
|
@ -345,7 +353,8 @@ def process_indirect(key, offset, resolve=True):
|
||||||
Set resolve to False if playback should be kicked off directly, not via
|
Set resolve to False if playback should be kicked off directly, not via
|
||||||
setResolvedUrl
|
setResolvedUrl
|
||||||
"""
|
"""
|
||||||
LOG.info('process_indirect called with key: %s, offset: %s', key, offset)
|
LOG.info('process_indirect called with key: %s, offset: %s, resolve: %s',
|
||||||
|
key, offset, resolve)
|
||||||
global RESOLVE
|
global RESOLVE
|
||||||
RESOLVE = resolve
|
RESOLVE = resolve
|
||||||
result = Playback_Successful()
|
result = Playback_Successful()
|
||||||
|
|
|
@ -1,156 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
from logging import getLogger
|
|
||||||
import copy
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
|
|
||||||
import kodidb_functions as kodidb
|
|
||||||
import plexdb_functions as plexdb
|
|
||||||
from downloadutils import DownloadUtils as DU
|
|
||||||
from plexbmchelper.subscribers import LOCKER
|
|
||||||
from utils import kodi_time_to_millis, unix_date_to_kodi, unix_timestamp
|
|
||||||
import variables as v
|
|
||||||
import state
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
LOG = getLogger("PLEX." + __name__)
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
|
|
||||||
@LOCKER.lockthis
|
|
||||||
def playback_cleanup(ended=False):
|
|
||||||
"""
|
|
||||||
PKC cleanup after playback ends/is stopped. Pass ended=True if Kodi
|
|
||||||
completely finished playing an item (because we will get and use wrong
|
|
||||||
timing data otherwise)
|
|
||||||
"""
|
|
||||||
LOG.debug('playback_cleanup called')
|
|
||||||
# We might have saved a transient token from a user flinging media via
|
|
||||||
# Companion (if we could not use the playqueue to store the token)
|
|
||||||
state.PLEX_TRANSIENT_TOKEN = None
|
|
||||||
for playerid in state.ACTIVE_PLAYERS:
|
|
||||||
status = state.PLAYER_STATES[playerid]
|
|
||||||
# Remember the last played item later
|
|
||||||
state.OLD_PLAYER_STATES[playerid] = copy.deepcopy(status)
|
|
||||||
# Stop transcoding
|
|
||||||
if status['playmethod'] == 'Transcode':
|
|
||||||
LOG.debug('Tell the PMS to stop transcoding')
|
|
||||||
DU().downloadUrl(
|
|
||||||
'{server}/video/:/transcode/universal/stop',
|
|
||||||
parameters={'session': v.PKC_MACHINE_IDENTIFIER})
|
|
||||||
if playerid == 1:
|
|
||||||
# Bookmarks might not be pickup up correctly, so let's do them
|
|
||||||
# manually. Applies to addon paths, but direct paths might have
|
|
||||||
# started playback via PMS
|
|
||||||
_record_playstate(status, ended)
|
|
||||||
# Reset the player's status
|
|
||||||
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
|
|
||||||
# As all playback has halted, reset the players that have been active
|
|
||||||
state.ACTIVE_PLAYERS = []
|
|
||||||
LOG.debug('Finished PKC playback cleanup')
|
|
||||||
|
|
||||||
|
|
||||||
def _record_playstate(status, ended):
|
|
||||||
if not status['plex_id']:
|
|
||||||
LOG.debug('No Plex id found to record playstate for status %s', status)
|
|
||||||
return
|
|
||||||
with plexdb.Get_Plex_DB() as plex_db:
|
|
||||||
kodi_db_item = plex_db.getItem_byId(status['plex_id'])
|
|
||||||
if kodi_db_item is None:
|
|
||||||
# Item not (yet) in Kodi library
|
|
||||||
LOG.debug('No playstate update due to Plex id not found: %s', status)
|
|
||||||
return
|
|
||||||
totaltime = float(kodi_time_to_millis(status['totaltime'])) / 1000
|
|
||||||
if ended:
|
|
||||||
progress = 0.99
|
|
||||||
time = v.IGNORE_SECONDS_AT_START + 1
|
|
||||||
else:
|
|
||||||
time = float(kodi_time_to_millis(status['time'])) / 1000
|
|
||||||
try:
|
|
||||||
progress = time / totaltime
|
|
||||||
except ZeroDivisionError:
|
|
||||||
progress = 0.0
|
|
||||||
LOG.debug('Playback progress %s (%s of %s seconds)',
|
|
||||||
progress, time, totaltime)
|
|
||||||
playcount = status['playcount']
|
|
||||||
last_played = unix_date_to_kodi(unix_timestamp())
|
|
||||||
if playcount is None:
|
|
||||||
LOG.debug('playcount not found, looking it up in the Kodi DB')
|
|
||||||
with kodidb.GetKodiDB('video') as kodi_db:
|
|
||||||
playcount = kodi_db.get_playcount(kodi_db_item[1])
|
|
||||||
playcount = 0 if playcount is None else playcount
|
|
||||||
if time < v.IGNORE_SECONDS_AT_START:
|
|
||||||
LOG.debug('Ignoring playback less than %s seconds',
|
|
||||||
v.IGNORE_SECONDS_AT_START)
|
|
||||||
# Annoying Plex bug - it'll reset an already watched video to unwatched
|
|
||||||
playcount = None
|
|
||||||
last_played = None
|
|
||||||
time = 0
|
|
||||||
elif progress >= v.MARK_PLAYED_AT:
|
|
||||||
LOG.debug('Recording entirely played video since progress > %s',
|
|
||||||
v.MARK_PLAYED_AT)
|
|
||||||
playcount += 1
|
|
||||||
time = 0
|
|
||||||
with kodidb.GetKodiDB('video') as kodi_db:
|
|
||||||
kodi_db.addPlaystate(kodi_db_item[1],
|
|
||||||
time,
|
|
||||||
totaltime,
|
|
||||||
playcount,
|
|
||||||
last_played)
|
|
||||||
# Hack to force "in progress" widget to appear if it wasn't visible before
|
|
||||||
if (state.FORCE_RELOAD_SKIN and
|
|
||||||
xbmc.getCondVisibility('Window.IsVisible(Home.xml)')):
|
|
||||||
LOG.debug('Refreshing skin to update widgets')
|
|
||||||
xbmc.executebuiltin('ReloadSkin()')
|
|
||||||
|
|
||||||
|
|
||||||
class PKC_Player(xbmc.Player):
|
|
||||||
def __init__(self):
|
|
||||||
xbmc.Player.__init__(self)
|
|
||||||
LOG.info("Started playback monitor.")
|
|
||||||
|
|
||||||
def onPlayBackStarted(self):
|
|
||||||
"""
|
|
||||||
Will be called when xbmc starts playing a file.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onPlayBackPaused(self):
|
|
||||||
"""
|
|
||||||
Will be called when playback is paused
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onPlayBackResumed(self):
|
|
||||||
"""
|
|
||||||
Will be called when playback is resumed
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onPlayBackSeek(self, time, seekOffset):
|
|
||||||
"""
|
|
||||||
Will be called when user seeks to a certain time during playback
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onPlayBackStopped(self):
|
|
||||||
"""
|
|
||||||
Will be called when playback is stopped by the user
|
|
||||||
"""
|
|
||||||
LOG.debug("ONPLAYBACK_STOPPED")
|
|
||||||
playback_cleanup()
|
|
||||||
|
|
||||||
def onPlayBackEnded(self):
|
|
||||||
"""
|
|
||||||
Will be called when playback ends due to the media file being finished
|
|
||||||
"""
|
|
||||||
LOG.debug("ONPLAYBACK_ENDED")
|
|
||||||
if state.PKC_CAUSED_STOP is True:
|
|
||||||
state.PKC_CAUSED_STOP = False
|
|
||||||
LOG.debug('PKC caused this playback stop - ignoring')
|
|
||||||
else:
|
|
||||||
playback_cleanup(ended=True)
|
|
|
@ -49,6 +49,9 @@ class PlaylistObjectBaseclase(object):
|
||||||
self.plex_transient_token = None
|
self.plex_transient_token = None
|
||||||
# Need a hack for detecting swaps of elements
|
# Need a hack for detecting swaps of elements
|
||||||
self.old_kodi_pl = []
|
self.old_kodi_pl = []
|
||||||
|
# Did PKC itself just change the playqueue so the PKC playqueue monitor
|
||||||
|
# should not pick up any changes?
|
||||||
|
self.pkc_edit = False
|
||||||
# Workaround to avoid endless loops of detecting PL clears
|
# Workaround to avoid endless loops of detecting PL clears
|
||||||
self._clear_list = []
|
self._clear_list = []
|
||||||
|
|
||||||
|
@ -64,6 +67,8 @@ class PlaylistObjectBaseclase(object):
|
||||||
if isinstance(getattr(self, key), str):
|
if isinstance(getattr(self, key), str):
|
||||||
answ += '\'%s\': \'%s\', ' % (key,
|
answ += '\'%s\': \'%s\', ' % (key,
|
||||||
try_decode(getattr(self, key)))
|
try_decode(getattr(self, key)))
|
||||||
|
elif isinstance(getattr(self, key), unicode):
|
||||||
|
answ += '\'%s\': \'%s\', ' % (key, getattr(self, key))
|
||||||
else:
|
else:
|
||||||
# e.g. int
|
# e.g. int
|
||||||
answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key)))
|
answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key)))
|
||||||
|
@ -184,6 +189,8 @@ class Playlist_Item(object):
|
||||||
if isinstance(getattr(self, key), str):
|
if isinstance(getattr(self, key), str):
|
||||||
answ += '\'%s\': \'%s\', ' % (key,
|
answ += '\'%s\': \'%s\', ' % (key,
|
||||||
try_decode(getattr(self, key)))
|
try_decode(getattr(self, key)))
|
||||||
|
elif isinstance(getattr(self, key), unicode):
|
||||||
|
answ += '\'%s\': \'%s\', ' % (key, getattr(self, key))
|
||||||
else:
|
else:
|
||||||
# e.g. int
|
# e.g. int
|
||||||
answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key)))
|
answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key)))
|
||||||
|
|
|
@ -226,17 +226,26 @@ class PlayqueueMonitor(Thread):
|
||||||
if stopped():
|
if stopped():
|
||||||
break
|
break
|
||||||
sleep(1000)
|
sleep(1000)
|
||||||
for playqueue in PLAYQUEUES:
|
work = []
|
||||||
kodi_pl = js.playlist_get_items(playqueue.playlistid)
|
# Detect changed playqueues first, do the work afterwards
|
||||||
if playqueue.old_kodi_pl != kodi_pl:
|
with LOCK:
|
||||||
if playqueue.id is None and (not state.DIRECT_PATHS or
|
for playqueue in PLAYQUEUES:
|
||||||
state.CONTEXT_MENU_PLAY):
|
kodi_pl = js.playlist_get_items(playqueue.playlistid)
|
||||||
# Only initialize if directly fired up using direct
|
if playqueue.old_kodi_pl != kodi_pl:
|
||||||
# paths. Otherwise let default.py do its magic
|
if playqueue.id is None and (not state.DIRECT_PATHS or
|
||||||
LOG.debug('Not yet initiating playback')
|
state.CONTEXT_MENU_PLAY):
|
||||||
else:
|
# Only initialize if directly fired up using direct
|
||||||
# compare old and new playqueue
|
# paths. Otherwise let default.py do its magic
|
||||||
self._compare_playqueues(playqueue, kodi_pl)
|
LOG.debug('Not yet initiating playback')
|
||||||
playqueue.old_kodi_pl = list(kodi_pl)
|
elif playqueue.pkc_edit:
|
||||||
|
playqueue.pkc_edit = False
|
||||||
|
LOG.debug('PKC edited the playqueue - skipping')
|
||||||
|
else:
|
||||||
|
# We do need to update our playqueues
|
||||||
|
work.append((playqueue, kodi_pl))
|
||||||
|
playqueue.old_kodi_pl = kodi_pl
|
||||||
|
# Now do the work - LOCK individual playqueue edits
|
||||||
|
for playqueue, kodi_pl in work:
|
||||||
|
self._compare_playqueues(playqueue, kodi_pl)
|
||||||
sleep(200)
|
sleep(200)
|
||||||
LOG.info("----===## PlayqueueMonitor stopped ##===----")
|
LOG.info("----===## PlayqueueMonitor stopped ##===----")
|
||||||
|
|
|
@ -1,17 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from utils import kodi_sql
|
from utils import kodi_sql
|
||||||
import variables as v
|
import variables as v
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
log = getLogger("PLEX."+__name__)
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
|
|
||||||
class Get_Plex_DB():
|
class Get_Plex_DB():
|
||||||
"""
|
"""
|
||||||
|
@ -183,34 +178,15 @@ class Plex_DB_Functions():
|
||||||
None if not found
|
None if not found
|
||||||
"""
|
"""
|
||||||
query = '''
|
query = '''
|
||||||
SELECT plex_id
|
SELECT plex_id FROM plex WHERE kodi_fileid = ? AND kodi_type = ?
|
||||||
FROM plex
|
LIMIT 1
|
||||||
WHERE kodi_fileid = ? AND kodi_type = ?
|
|
||||||
'''
|
'''
|
||||||
|
self.plexcursor.execute(query, (kodi_fileid, kodi_type))
|
||||||
try:
|
try:
|
||||||
self.plexcursor.execute(query, (kodi_fileid, kodi_type))
|
|
||||||
item = self.plexcursor.fetchone()[0]
|
item = self.plexcursor.fetchone()[0]
|
||||||
return item
|
except TypeError:
|
||||||
except:
|
item = None
|
||||||
return None
|
return item
|
||||||
|
|
||||||
def getMusicItem_byFileId(self, kodi_id, kodi_type):
|
|
||||||
"""
|
|
||||||
Returns the plex_id for kodi_id and kodi_type
|
|
||||||
|
|
||||||
None if not found
|
|
||||||
"""
|
|
||||||
query = '''
|
|
||||||
SELECT plex_id
|
|
||||||
FROM plex
|
|
||||||
WHERE kodi_id = ? AND kodi_type = ?
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
self.plexcursor.execute(query, (kodi_id, kodi_type))
|
|
||||||
item = self.plexcursor.fetchone()[0]
|
|
||||||
return item
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getItem_byId(self, plex_id):
|
def getItem_byId(self, plex_id):
|
||||||
"""
|
"""
|
||||||
|
@ -237,7 +213,7 @@ class Plex_DB_Functions():
|
||||||
FROM plex
|
FROM plex
|
||||||
WHERE plex_id LIKE ?
|
WHERE plex_id LIKE ?
|
||||||
'''
|
'''
|
||||||
self.plexcursor.execute(query, (plex_id+"%",))
|
self.plexcursor.execute(query, (plex_id + "%",))
|
||||||
return self.plexcursor.fetchall()
|
return self.plexcursor.fetchall()
|
||||||
|
|
||||||
def getItem_byView(self, view_id):
|
def getItem_byView(self, view_id):
|
||||||
|
@ -260,8 +236,7 @@ class Plex_DB_Functions():
|
||||||
query = '''
|
query = '''
|
||||||
SELECT plex_id, parent_id, plex_type
|
SELECT plex_id, parent_id, plex_type
|
||||||
FROM plex
|
FROM plex
|
||||||
WHERE kodi_id = ?
|
WHERE kodi_id = ? AND kodi_type = ?
|
||||||
AND kodi_type = ?
|
|
||||||
'''
|
'''
|
||||||
self.plexcursor.execute(query, (kodi_id, kodi_type,))
|
self.plexcursor.execute(query, (kodi_id, kodi_type,))
|
||||||
return self.plexcursor.fetchone()
|
return self.plexcursor.fetchone()
|
||||||
|
@ -304,24 +279,6 @@ class Plex_DB_Functions():
|
||||||
self.plexcursor.execute(query, (plex_type,))
|
self.plexcursor.execute(query, (plex_type,))
|
||||||
return self.plexcursor.fetchall()
|
return self.plexcursor.fetchall()
|
||||||
|
|
||||||
def getMediaType_byId(self, plex_id):
|
|
||||||
"""
|
|
||||||
Returns plex_type for plex_id
|
|
||||||
|
|
||||||
Or None if not found
|
|
||||||
"""
|
|
||||||
query = '''
|
|
||||||
SELECT plex_type
|
|
||||||
FROM plex
|
|
||||||
WHERE plex_id = ?
|
|
||||||
'''
|
|
||||||
self.plexcursor.execute(query, (plex_id,))
|
|
||||||
try:
|
|
||||||
itemtype = self.plexcursor.fetchone()[0]
|
|
||||||
except TypeError:
|
|
||||||
itemtype = None
|
|
||||||
return itemtype
|
|
||||||
|
|
||||||
def addReference(self, plex_id, plex_type, kodi_id, kodi_type,
|
def addReference(self, plex_id, plex_type, kodi_id, kodi_type,
|
||||||
kodi_fileid=None, kodi_pathid=None, parent_id=None,
|
kodi_fileid=None, kodi_pathid=None, parent_id=None,
|
||||||
checksum=None, view_id=None):
|
checksum=None, view_id=None):
|
||||||
|
@ -381,13 +338,6 @@ class Plex_DB_Functions():
|
||||||
self.plexcursor.execute('DELETE FROM plex WHERE plex_id = ?',
|
self.plexcursor.execute('DELETE FROM plex WHERE plex_id = ?',
|
||||||
(plex_id,))
|
(plex_id,))
|
||||||
|
|
||||||
def removeWildItem(self, plex_id):
|
|
||||||
"""
|
|
||||||
Removes all entries with plex_id with % added
|
|
||||||
"""
|
|
||||||
query = "DELETE FROM plex WHERE plex_id LIKE ?"
|
|
||||||
self.plexcursor.execute(query, (plex_id+"%",))
|
|
||||||
|
|
||||||
def itemsByType(self, plex_type):
|
def itemsByType(self, plex_type):
|
||||||
"""
|
"""
|
||||||
Returns a list of dicts for plex_type:
|
Returns a list of dicts for plex_type:
|
||||||
|
@ -418,7 +368,7 @@ class Plex_DB_Functions():
|
||||||
"""
|
"""
|
||||||
Sets the fanart_synced flag to 1 for plex_id
|
Sets the fanart_synced flag to 1 for plex_id
|
||||||
"""
|
"""
|
||||||
query = '''UPDATE plex SET fanart_synced = 1 WHERE plex_id = ?'''
|
query = 'UPDATE plex SET fanart_synced = 1 WHERE plex_id = ?'
|
||||||
self.plexcursor.execute(query, (plex_id,))
|
self.plexcursor.execute(query, (plex_id,))
|
||||||
|
|
||||||
def get_missing_fanart(self):
|
def get_missing_fanart(self):
|
||||||
|
|
|
@ -95,8 +95,12 @@ WEBSOCKET_QUEUE = None
|
||||||
|
|
||||||
# Which Kodi player is/has been active? (either int 1, 2 or 3)
|
# Which Kodi player is/has been active? (either int 1, 2 or 3)
|
||||||
ACTIVE_PLAYERS = []
|
ACTIVE_PLAYERS = []
|
||||||
# Failsafe for throwing failing ListItems() back to Kodi's setResolvedUrl
|
# Failsafe for throwing an empty video back to Kodi's setResolvedUrl to set
|
||||||
|
# up our own playlist from the very beginning
|
||||||
PKC_CAUSED_STOP = False
|
PKC_CAUSED_STOP = False
|
||||||
|
# Flag if the 0 length PKC video has already failed so we can start resolving
|
||||||
|
# playback (set in player.py)
|
||||||
|
PKC_CAUSED_STOP_DONE = True
|
||||||
|
|
||||||
# Kodi player states - here, initial values are set
|
# Kodi player states - here, initial values are set
|
||||||
PLAYER_STATES = {
|
PLAYER_STATES = {
|
||||||
|
|
Loading…
Reference in a new issue