Merge branch 'master' into translations

This commit is contained in:
Croneter 2018-04-10 19:31:10 +02:00
commit bc36231454
15 changed files with 372 additions and 429 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 ##===----")

View file

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

View file

@ -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 = {