Compare commits

...

1942 commits

Author SHA1 Message Date
croneter
ee1eb14476
Merge pull request #1672 from croneter/beta-version
Bump Python 2 Master
2021-10-17 12:08:34 +02:00
croneter
9e54f59fd4
Merge pull request #1671 from croneter/version-bump
Beta and stable version bump 2.15.0
2021-10-17 12:07:54 +02:00
croneter
4cfcc4c1f8 Beta and stable version bump 2.15.0 2021-10-17 12:07:18 +02:00
croneter
5a6623a1dc
Merge pull request #1670 from croneter/update-translations
Update translations from Transifex [backport]
2021-10-17 11:56:31 +02:00
croneter
e5fa5de670 Update translations from Transifex 2021-10-17 11:55:04 +02:00
croneter
191a3131e3
Merge pull request #1669 from croneter/stream-option
Add playback settings to let the user choose whether Plex or Kodi provides the default audio or subtitle stream on playback start [backport]
2021-10-17 11:54:19 +02:00
croneter
f96c246244 Add playback settings to let the user choose whether Plex or Kodi provides the default audio and subtitle streams on playback start 2021-10-17 11:52:22 +02:00
croneter
a4bf3d061a
Merge pull request #1668 from croneter/fix-subs
Refactor stream code and fix Kodi not activating subtitle when it should [backport]
2021-10-17 11:51:04 +02:00
croneter
24c1ada5b1
Merge pull request #1667 from croneter/typeerror
Direct Paths: Fix TypeError: "element indices must be integers" on playback startup [backport]
2021-10-17 11:50:53 +02:00
croneter
61114e0d2e Refactor and fix Kodi not activating subtitle when it should 2021-10-17 11:47:41 +02:00
croneter
bdc98d0352 Fix whitespace 2021-10-17 11:47:27 +02:00
croneter
436d2e4391 Direct Paths: Fix TypeError: element indices must be integers for subtitles 2021-10-17 11:44:57 +02:00
croneter
2bc98f9ff1
Merge pull request #1639 from croneter/version-bump
Beta version bump 2.14.4
2021-09-24 17:41:16 +02:00
croneter
a4fba553f3
Merge pull request #1634 from croneter/fix-logging
Fix logging if fanart.tv lookup fails: be less verbose
2021-09-24 17:41:05 +02:00
croneter
097fd4cfa2
Merge pull request #1638 from croneter/streams
Tell the PMS if a video's audio stream or potentially subtitle stream has changed. For subtitles, this functionality is broken due to a Kodi bug
2021-09-24 17:40:52 +02:00
croneter
d80d3525b3 Beta version bump 2.14.4 2021-09-24 17:39:56 +02:00
croneter
d54307ffd5 Tell the PMS if a video's audio stream or potentially subtitle stream has changed. For subtitles, this functionality is broken due to a Kodi bug 2021-09-24 17:32:01 +02:00
croneter
53e3258517
Merge pull request #1637 from croneter/refactor-part
Refactor usage of a media part's id
2021-09-24 17:30:11 +02:00
croneter
dae123acee
Merge pull request #1635 from croneter/fix-subs
Transcoding: Fix Plex burning-in subtitles when it should not
2021-09-24 17:29:54 +02:00
croneter
f4a0789fc0
Merge pull request #1636 from croneter/fix-streams
Large refactoring of playlist and playqueue code
2021-09-24 17:27:21 +02:00
croneter
887f659b2f Refactor usage of a media part's id 2021-09-24 17:22:04 +02:00
croneter
2bd692e173 Refactoring: playlist and playqueue items to use API instead of xml 2021-09-24 17:19:50 +02:00
croneter
176fa07e80 Refactoring: move all exceptions in a single module 2021-09-24 17:19:50 +02:00
croneter
9da61a059f Disentangle and optimize some code
Rename method

Simplify some code

Clarify some code
2021-09-24 17:11:17 +02:00
croneter
11d06d909e Transcoding: Fix Plex burning-in subtitles when it should not 2021-09-24 17:07:56 +02:00
croneter
e96df700c1 Fix logging if fanart.tv lookup fails 2021-09-24 16:59:56 +02:00
croneter
057921b05e Fix download not always returning entire requests.response object 2021-09-24 16:59:41 +02:00
croneter
63bd85d5c8
Merge pull request #1620 from croneter/version-bump
Beta version bump 2.14.3
2021-09-09 15:04:33 +02:00
croneter
9d6bae3957
Merge pull request #1619 from croneter/reset-resume
Implement "Reset resume position" from the Kodi context menu
2021-09-09 15:04:16 +02:00
croneter
2432ce5ee6 Beta version bump 2.14.3 2021-09-09 14:59:02 +02:00
croneter
5a009b7ea0 Implement Kodi's "Reset resume position" 2021-09-09 14:54:37 +02:00
croneter
26073e5dac
Merge pull request #1616 from croneter/beta-version
Update README.md
2021-09-08 12:37:39 +02:00
croneter
47cd15baa0
Update README.md 2021-09-08 12:36:07 +02:00
croneter
560fc5b9c8
Merge pull request #1615 from croneter/beta-version
Bump Python2 Master
2021-09-08 11:46:53 +02:00
croneter
9495e1e27d
Merge pull request #1614 from croneter/version-bump
Stable and beta version bump 2.14.2
2021-09-08 11:46:01 +02:00
croneter
5720811a7e
Merge pull request #1613 from croneter/readme
Update readme
2021-09-08 11:43:50 +02:00
croneter
45afba1840 Stable and beta version bump 2.14.2 2021-09-08 11:41:35 +02:00
croneter
cb1a3e74e0 Update readme 2021-09-08 11:39:25 +02:00
croneter
76c4fba8e6
Merge pull request #1608 from croneter/version-bump
Beta version bump 2.14.1
2021-09-04 16:48:05 +02:00
croneter
516a09ce56
Merge pull request #1607 from croneter/pick-plex-subs
Use Plex settings for audio and subtitle stream selection. This is a best guess regarding subtitles as Plex and Kodi are not sharing much info
2021-09-04 16:47:43 +02:00
croneter
41855882ab
Merge pull request #1606 from croneter/fix-subs
Fix PlexKodiConnect setting the Plex subtitle to None
2021-09-04 16:46:50 +02:00
croneter
289266bb81
Merge pull request #1598 from geropan/beta-version
Download landscape artwork from fanart.tv
2021-09-04 16:46:38 +02:00
croneter
0490ce766e Beta version bump 2.14.1 2021-09-04 16:41:56 +02:00
croneter
c182b8f5f8 subtitles.py: Backport for Python 2 2021-09-04 16:37:56 +02:00
croneter
e6a0af4621 Use Plex settings for audio and subtitle stream selection 2021-09-04 16:17:47 +02:00
croneter
ea877b55d5
Merge pull request #1605 from croneter/revert-sub
Revert "Fix PlexKodiConnect changing subtitles for all videos on the PMS
2021-09-04 16:11:25 +02:00
croneter
7c2478a568 Fix PlexKodiConnect setting the Plex subtitle to None 2021-09-04 16:09:15 +02:00
croneter
c99db1edff Revert "Fix PlexKodiConnect changing subtitles for all videos on the PMS"
This reverts commit 4de0920bf5.
2021-09-04 16:06:30 +02:00
Antonio Martin
2f25ba2eae Used general term in fanart.tv mapping for prefix completion 2021-08-25 21:37:16 +01:00
Antonio Martin
7602f02bcd Download landscape artwork from fanart.tv 2021-08-25 18:17:04 +01:00
croneter
de9c935a40
Merge pull request #1597 from croneter/beta-version
Bump master
2021-08-22 20:43:02 +02:00
croneter
e049f37da9
Merge pull request #1596 from croneter/version-bump
Beta and stable version bump 2.14.0
2021-08-22 20:42:04 +02:00
croneter
ce6ab2c258 Beta and stable version bump 2.14.0 2021-08-22 20:41:32 +02:00
croneter
0f7410e0e3
Merge pull request #1595 from croneter/fix-subtitles
Fix PlexKodiConnect changing subtitles for all videos on the PMS
2021-08-22 20:39:32 +02:00
croneter
4de0920bf5 Fix PlexKodiConnect changing subtitles for all videos on the PMS 2021-08-22 20:38:51 +02:00
croneter
2b9594dd90
Merge pull request #1560 from croneter/version-bump
Beta version bump 2.13.2
2021-07-25 11:22:30 +02:00
croneter
4f75502a8a
Merge pull request #1559 from croneter/fix-websocket
Websocket Fix AttributeError: 'NoneType' object has no attribute 'is_ssl'
2021-07-25 11:22:12 +02:00
croneter
6bf41116cb
Merge pull request #1558 from croneter/fix-recursion
Fix RecursionError: maximum recursion depth exceeded
2021-07-25 11:21:54 +02:00
croneter
6201a04513
Merge pull request #1557 from croneter/fix-racing
Fix a racing condition that could lead to the sync getting stuck
2021-07-25 11:21:04 +02:00
croneter
74ec9eff97 Beta version bump 2.13.2 2021-07-25 11:20:21 +02:00
croneter
295f403c64 Websocket Fix AttributeError: 'NoneType' object has no attribute is_ssl 2021-07-25 11:17:07 +02:00
croneter
cb8dc30c7c Fix RecursionError: maximum recursion depth exceeded 2021-07-25 11:13:46 +02:00
croneter
262315c3e7 Fix Regression: AttributeError 2021-07-25 11:11:31 +02:00
croneter
a18b971564 Fix a racing condition that could lead to the sync getting stuck 2021-07-25 11:11:00 +02:00
croneter
1bd1da9f5a
Merge pull request #1549 from croneter/version-bump
Beta version bump 2.13.1
2021-07-23 14:48:04 +02:00
croneter
2fd91ff9d6
Merge pull request #1548 from croneter/fix-race
Fix a racing condition that could lead to the sync process getting stuck
2021-07-23 14:47:52 +02:00
croneter
1001df5e30
Merge pull request #1547 from croneter/locked-database
Fix likelyhood of `database is locked` error occuring
2021-07-23 14:47:30 +02:00
croneter
0f2fd110db Beta version bump 2.13.1 2021-07-23 14:46:45 +02:00
croneter
ada337c2c4 Fix a racing condition that could lead to the sync getting stuck
Fixup racing
2021-07-23 14:38:18 +02:00
croneter
1066f857a2 Switch to context manager 2021-07-23 14:38:06 +02:00
croneter
858a33f816 Remove obsolete methods 2021-07-23 14:37:30 +02:00
croneter
fce964cc7b Improve logging
fixup logging
2021-07-23 14:36:08 +02:00
croneter
7c903d0c94 Fix likelyhood of database is locked error occuring 2021-07-23 14:34:42 +02:00
croneter
3ff97d0669
Merge pull request #1536 from croneter/beta-version
Bump master
2021-07-18 12:04:58 +02:00
croneter
7553061945
Merge pull request #1533 from croneter/version-bump
Stable and beta version bump 2.13.0
2021-07-18 12:03:28 +02:00
croneter
6105a571c8
Merge pull request #1535 from croneter/hama
Support forced HAMA IDs when using tvdb uniqueID
2021-07-18 12:03:04 +02:00
croneter
2484cf10ac
Merge pull request #1534 from croneter/ani-db
Support for the Plex HAMA agent to let Kodi identify animes (using Kodi's uniqueID 'anidb')
2021-07-18 12:02:43 +02:00
croneter
f171785602 Stable and beta version bump 2.13.0
fixup
2021-07-18 12:01:56 +02:00
BrutuZ
3e9c8c6361 Support for the Plex HAMA agent to let Kodi identify animes (using Kodi's uniqueID 'anidb')
Support HAMA's forced AniDB IDs
2021-07-18 11:59:32 +02:00
BrutuZ
cac32cc66a Support forced HAMA IDs when using tvdb uniqueID 2021-07-18 11:52:25 +02:00
croneter
c4d14c02e2
Merge pull request #1518 from croneter/version-bump
Beta version bump 2.12.26
2021-06-05 15:50:32 +02:00
croneter
c6056b4efc
Merge pull request #1521 from croneter/update-translations
Update translations from Transifex
2021-06-05 15:50:14 +02:00
croneter
2c979fba57
Merge pull request #1516 from croneter/fix-versions
Fix auto-picking of video stream if several video versions are available
2021-06-05 15:50:00 +02:00
croneter
f877c37e76
Merge pull request #1514 from croneter/fix-continue-watching
Add an additional Plex Hub "PKC Continue Watching" that merges the Plex Continue Watching with On Deck
2021-06-05 15:49:43 +02:00
croneter
038960c538 Update translations from Transifex 2021-06-05 15:43:01 +02:00
croneter
cdf1514215 Beta version bump 2.12.26 2021-06-05 15:01:50 +02:00
croneter
f15ef8886a Fix auto-picking of video stream if several video versions are available 2021-06-05 14:55:26 +02:00
croneter
7f8339a753 Add an additional Plex Hub "PKC Continue Watching" that merges the Plex Continue Watching with On Deck 2021-06-05 14:11:43 +02:00
croneter
0cf35b7b87
Merge pull request #1510 from croneter/beta-version
Bump master
2021-05-30 10:58:31 +02:00
croneter
09b0c61f11
Merge pull request #1509 from croneter/version-bump
Stable and beta version bump 2.12.25
2021-05-30 10:56:46 +02:00
croneter
675a8150cc Merge branch 'beta-version' of https://github.com/croneter/PlexKodiConnect into beta-version 2021-05-30 10:53:47 +02:00
croneter
a2194a5ce8 Stable and beta version bump 2.12.25 2021-05-30 10:53:24 +02:00
croneter
166b94c4cd
Merge pull request #1491 from croneter/update-websockets
Update websocket client to 0.59.0. Fix threading issues and AttributeErrors
2021-05-30 10:51:37 +02:00
croneter
46f99901cc Attempt to fix websocket threading issues and AttributeError: 'NoneType' object has no attribute 'is_ssl' or 'settimeout' 2021-05-29 16:45:35 +02:00
croneter
36befcf46a Fix websockets and AttributeError: 'NoneType' object has no attribute 2021-05-29 16:43:56 +02:00
croneter
abd8b04ff9 Update websocket client to 0.59.0 2021-05-24 13:09:00 +02:00
croneter
dbf2117a30
Merge pull request #1484 from croneter/beta-version
Bump master
2021-05-23 08:05:07 +02:00
croneter
a2e08a30ec
Merge pull request #1483 from croneter/version-bump
Beta and stable version bump 2.12.24
2021-05-23 08:04:33 +02:00
croneter
d38fe789b3 Beta and stable version bump 2.12.24 2021-05-23 08:03:45 +02:00
croneter
f262fba18a
Merge pull request #1474 from croneter/version-bump
Beta version bump 2.12.23
2021-05-14 09:18:45 +02:00
croneter
29822db781
Merge pull request #1469 from croneter/py2-fix-dict
Fix Alexa and RuntimeError: dictionary keys changed during iteration
2021-05-14 09:18:32 +02:00
croneter
7c12b7aa36
Merge pull request #1460 from croneter/fix-attributeerror
Fix a rare AttributeError when using playlists
2021-05-14 09:18:11 +02:00
croneter
019bd1aeae Beta version bump 2.12.23 2021-05-14 09:17:26 +02:00
croneter
c29be48cac Fix Alexa and RuntimeError: dictionary keys changed during iteration 2021-05-02 14:36:34 +02:00
croneter
4916bbb46e Fix a rare AttributeError when using playlists 2021-04-30 10:22:11 +02:00
croneter
f7ae807167
Merge pull request #1454 from croneter/beta-version
Bump master
2021-04-17 14:30:38 +02:00
croneter
966cf6f526
Merge pull request #1453 from croneter/version-bump
Beta and stable version bump 2.12.22
2021-04-17 14:29:49 +02:00
croneter
ce14d394d4 Beta and stable version bump 2.12.22 2021-04-17 14:28:25 +02:00
croneter
46f115de68 Merge branch 'version-bump' into beta-version 2021-03-20 14:43:03 +01:00
croneter
e98aca1f00 Merge branch 'beta-version' into version-bump 2021-03-20 14:42:42 +01:00
croneter
cb6ba50904
Merge pull request #1420 from croneter/fix-settings
Hopefully fix RuntimeError: no add-on id "plugin.video.plexkodiconnect"
2021-03-20 14:40:36 +01:00
croneter
2d02f4af07
Merge pull request #1423 from croneter/update-translations
Update translations
2021-03-20 14:40:03 +01:00
croneter
1493ac0c58 Update readme 2021-03-20 14:36:09 +01:00
croneter
7c57dca0ec Update translations in addon.xml 2021-03-20 14:36:08 +01:00
croneter
04e2d09835 Update translations from Transifex 2021-03-20 14:36:08 +01:00
croneter
fbfcffbb0c Beta version bump 2.12.21 2021-03-20 14:16:14 +01:00
croneter
6b6464dac3 Hopefully fix RuntimeError: no add-on id "plugin.video.plexkodiconnect" 2021-03-20 10:33:59 +01:00
croneter
34045c0136
Merge pull request #1419 from croneter/new-websockets
Switch to new websocket implementation
2021-03-20 10:01:37 +01:00
croneter
1c4b15e357 Adapt websocket client logic 2021-03-20 09:48:23 +01:00
croneter
3d139b0929 Add new dependency on script.module.six 2021-03-20 09:26:45 +01:00
croneter
4c0634bc13 Add new Python websocket client 2021-03-20 09:25:18 +01:00
croneter
060880e754
Merge pull request #1406 from croneter/version-bump
Beta version bump 2.12.20
2021-03-14 14:36:45 +01:00
croneter
95758b5dc8
Merge pull request #1398 from croneter/add-websocket-info
Add information to PKC settings for background sync and Alexa whether a connection has been successfully made
2021-03-14 14:36:02 +01:00
croneter
3d7d2d0993 Beta version bump 2.12.20 2021-03-14 14:35:52 +01:00
croneter
808136bff8
Merge pull request #1405 from croneter/beta-version
Bump master for Phyton 2
2021-03-14 14:29:10 +01:00
croneter
98b6b681fd
Merge pull request #1404 from croneter/update-readme
Update readme
2021-03-14 14:28:20 +01:00
croneter
0a1edcd24a Update readme 2021-03-14 14:27:18 +01:00
croneter
c8caf2f11b
Merge pull request #1402 from croneter/version-bump
Beta and stable version bump 2.12.19
2021-03-14 14:21:14 +01:00
croneter
4bef20da32
Merge pull request #1403 from croneter/update-filename
Rename skip intro skin file
2021-03-14 14:20:57 +01:00
croneter
886d2e5df7 Rename skip intro skin file 2021-03-14 14:19:18 +01:00
croneter
f6c2a7c08f Beta and stable version bump 2.12.19 2021-03-14 14:18:02 +01:00
croneter
0fd7d11631 Add information to PKC settings about status of websocket and Alexa websocket connections 2021-03-14 13:58:12 +01:00
croneter
c69d131084
Merge pull request #1388 from croneter/versionbump
Beta version bump 2.12.18
2021-03-07 17:21:18 +01:00
croneter
dc5402abcc
Merge pull request #1385 from croneter/sync-playstate
Quickly sync recently watched items before synching the playstates of the entire Plex library
2021-03-07 17:20:47 +01:00
croneter
9d7d33c0d0
Merge pull request #1376 from croneter/fix-websocket
Improve logging for websocket JSON loads
2021-03-07 17:20:30 +01:00
croneter
1885d3fc94 Beta version bump 2.12.18 2021-03-07 17:17:37 +01:00
croneter
bb7b2de44b Sync recently watched items individually before synching every playstate 2021-03-07 15:21:38 +01:00
croneter
f134266efc Improve logging for websocket JSON loads 2021-03-01 10:48:12 +01:00
croneter
66771c53a2
Merge pull request #1366 from croneter/version-bump
Beta version bump 2.12.17
2021-02-24 17:42:28 +01:00
croneter
16cbe430af
Merge pull request #1365 from croneter/seasonnames
Sync name and user rating of a TV show season to Kodi
2021-02-24 17:42:15 +01:00
croneter
8aa5890e67
Merge pull request #1362 from croneter/fix-typeerror
Fix rare TypeError: expected string or buffer on playback start
2021-02-24 17:41:50 +01:00
croneter
12587a985c Beta version bump 2.12.17 2021-02-24 17:38:30 +01:00
croneter
9150e168f6 Sync name and user rating of a TV show season to Kodi 2021-02-24 17:22:12 +01:00
croneter
a12e07da6a Fix rare TypeError: expected string or buffer on playback start 2021-02-24 15:21:07 +01:00
croneter
fad755745a
Merge pull request #1349 from croneter/beta-version
Bump master
2021-02-20 16:25:17 +01:00
croneter
07ed0d1105
Merge pull request #1348 from croneter/version-bump
Stable and beta version bump 2.12.16
2021-02-20 16:24:43 +01:00
croneter
f524018160 Stable and beta version bump 2.12.16 2021-02-20 16:22:47 +01:00
croneter
cf6a301d70
Merge pull request #1343 from croneter/version-bump
Beta version bump 2.12.15
2021-02-13 18:14:23 +01:00
croneter
09d4ed597b
Merge pull request #1339 from croneter/update-translations
Update translations
2021-02-13 18:13:56 +01:00
croneter
faf8575537
Merge pull request #1341 from croneter/fix-skip-intro
Fix skip intros sometimes not working due to a RuntimeError
2021-02-13 18:12:39 +01:00
croneter
c4cfdddb91 Beta version bump 2.12.15 2021-02-13 18:11:22 +01:00
croneter
f469627d33 Fix skip intros sometimes not working due to a RuntimeError 2021-02-13 18:08:05 +01:00
croneter
8bccff05b6 Update translations 2021-02-13 18:04:48 +01:00
croneter
08bbf38128
Merge pull request #1337 from croneter/py2-version-bump
Beta version bump 2.12.14
2021-02-09 21:10:39 +01:00
croneter
474e4ac5d1
Merge pull request #1336 from croneter/py2-skipintro
Add skip intro functionality
2021-02-09 21:10:23 +01:00
croneter
10326882bd Beta version bump 2.12.14 2021-02-09 19:47:54 +01:00
croneter
e980de05a8 Add skip intro functionality 2021-02-09 19:43:40 +01:00
croneter
0051ed316e
Merge pull request #1332 from croneter/beta-version
Bump master
2021-02-07 13:15:44 +01:00
croneter
c79938e08b
Merge pull request #1331 from croneter/py2-version-bump
Beta and stable version bump 2.12.13
2021-02-07 13:15:11 +01:00
croneter
3e1f52802f
Merge pull request #1329 from croneter/p2-fix-keyerror
Fix KeyError: u'game' if Plex Arcade has been activated
2021-02-07 13:14:55 +01:00
croneter
06a20a8358
Merge pull request #1327 from croneter/py2fix-attributeerror
Fix AttributeError: 'App' object has no attribute 'threads' when sync is cancelled
2021-02-07 13:14:38 +01:00
croneter
31549a1ffb Beta and stable version bump 2.12.13 2021-02-07 13:07:55 +01:00
croneter
a3d654c65c Fix KeyError: u'game' if Plex Arcade has been activated 2021-02-07 13:02:41 +01:00
croneter
dad8d58824 Fix AttributeError: 'App' object has no attribute 'threads' when sync is cancelled 2021-02-07 12:17:38 +01:00
croneter
e5585aec44
Merge pull request #1322 from croneter/beta-version
Bump master
2021-01-31 17:53:32 +01:00
croneter
a7ffceb631
Merge pull request #1321 from croneter/version-bump
Stable and beta version bump 2.12.12
2021-01-31 17:53:01 +01:00
croneter
538832bed5
Merge pull request #1320 from croneter/fix-valueerror
ValueError: invalid literal for int() for invalid dates sent by Plex
2021-01-31 17:52:45 +01:00
croneter
94e474513c
Merge pull request #1319 from croneter/fix-deadlock
Hopefully fix rare case when sync would get stuck indefinitely
2021-01-31 17:52:24 +01:00
croneter
1b56f5cef9 Stable and beta version bump 2.12.12 2021-01-31 17:49:59 +01:00
croneter
f192c0912c Hopefully fix rare case when sync would get stuck indefinitely 2021-01-31 17:48:02 +01:00
croneter
269dedf398 ValueError: invalid literal for int() for invalid dates sent by Plex 2021-01-31 17:45:38 +01:00
croneter
63144ba070
Merge pull request #1315 from croneter/version-bump
Beta version bump 2.12.11
2021-01-28 13:37:15 +01:00
croneter
625d4c91b4
Merge pull request #1314 from croneter/fix-streams
Fix PKC not auto-picking audio/subtitle stream when transcoding
2021-01-28 13:36:52 +01:00
croneter
011d20473e
Merge pull request #1313 from croneter/fix-valueerror
Fix ValueError when deleting a music album
2021-01-28 13:36:32 +01:00
croneter
2884054fd4
Merge pull request #1312 from croneter/os-error
Fix OSError: Invalid argument when Plex returns an invalid timestamp
2021-01-28 13:36:20 +01:00
croneter
01fb1d5da6 Beta version bump 2.12.11 2021-01-28 13:35:22 +01:00
croneter
f187111411 Fix PKC not auto-picking audio/subtitle stream when transcoding 2021-01-28 13:33:09 +01:00
croneter
281c7d1599 Fix ValueError when deleting a music album 2021-01-28 13:18:31 +01:00
croneter
89afd46b56 Fix OSError: Invalid argument when Plex returns an invalid timestamp 2021-01-28 13:12:48 +01:00
croneter
94a86b43c1
Merge pull request #1304 from croneter/beta-version
Bump master
2021-01-24 17:35:00 +01:00
croneter
7393023fcc
Merge pull request #1303 from croneter/version-bump
Stable and beta version bump 2.12.10
2021-01-24 17:21:43 +01:00
croneter
f544c4065f
Merge pull request #1302 from croneter/fix-pix
Fix pictures from Plex picture libraries not working/displaying
2021-01-24 17:21:25 +01:00
croneter
8ae2fdc10a Stable and beta version bump 2.12.10 2021-01-24 17:18:40 +01:00
croneter
b9c1aaac20 Add some additional exif picture metadata to listitems. But Kodi skins do not seem to be using that info, unfortunately 2021-01-24 17:14:59 +01:00
croneter
e887e7162b Fix pictures from Plex picture libraries not working/displaying 2021-01-24 17:12:28 +01:00
croneter
b2139ce150 Optimize capitalization 2021-01-24 17:12:11 +01:00
croneter
22efe274a1
Merge pull request #1279 from croneter/beta-version
Bump master
2021-01-11 20:40:24 +01:00
croneter
828a580031
Merge pull request #1278 from croneter/version-bumper
Beta and stable version bump 2.12.9
2021-01-11 20:39:26 +01:00
croneter
151e3a5eef
Merge pull request #1277 from croneter/fix-user
Fix Local variable 'user' referenced before assignement
2021-01-11 20:38:46 +01:00
croneter
939cdd4615 Beta and stable version bump 2.12.9 2021-01-11 20:37:47 +01:00
croneter
a867acb0f8 Fix Local variable 'user' referenced before assignement 2021-01-11 20:36:02 +01:00
croneter
acf446dcc0
Merge pull request #1275 from croneter/beta-version
Bump master
2021-01-11 16:27:33 +01:00
croneter
70e6e4350e
Merge pull request #1274 from croneter/version-bump
Beta and stable version bump 2.12.8
2021-01-11 16:26:24 +01:00
croneter
86dab2ab66 Beta and stable version bump 2.12.8 2021-01-11 16:25:57 +01:00
croneter
4bae675181
Merge pull request #1270 from croneter/version-bump
Beta version bump 2.12.7
2021-01-09 17:18:01 +01:00
croneter
250859d3a7
Merge pull request #1269 from croneter/fix-sets
Fix missing Kodi tags for movie collections/sets
2021-01-09 17:17:48 +01:00
croneter
3cc939f320
Merge pull request #1268 from croneter/fix-user
Fix PKC suddenly using main Plex user's credentials, e.g. when the PMS address changed
2021-01-09 17:17:26 +01:00
croneter
aac16f38b3 Fix missing Kodi tags for movie collections/sets 2021-01-09 17:16:28 +01:00
croneter
5014a0fafa Beta version bump 2.12.7 2021-01-09 17:14:56 +01:00
croneter
7cf8cb59f1 Improve logic when writing credentials to settings file 2021-01-09 17:00:18 +01:00
croneter
a648d8941a Improve some code 2021-01-09 16:54:39 +01:00
croneter
d1fdf5d25f Fix PKC using main user's token if PMS address changes 2021-01-03 17:17:17 +01:00
croneter
5eb1c2aacd
Merge pull request #1259 from croneter/beta-version
Bump master
2021-01-02 13:18:13 +01:00
croneter
9e0ac64bb9
Merge pull request #1258 from croneter/version-bump
Stable and beta version bump 2.12.6
2021-01-02 13:16:01 +01:00
croneter
5816235062
Merge pull request #1257 from croneter/update-translations
Update translations
2021-01-02 13:15:36 +01:00
croneter
0982c3bae2
Merge pull request #1256 from croneter/fix-keyerror
Fix rare KeyError when using PKC widgets
2021-01-02 13:15:18 +01:00
croneter
17d84c1f29
Merge pull request #1253 from croneter/fix-suspension
Fix suspension of artwork caching and PKC becoming unresponsive
2021-01-02 13:15:02 +01:00
croneter
d096854b14 Stable and beta version bump 2.12.6 2021-01-02 13:13:56 +01:00
croneter
ddf8637bb6 Update translations 2021-01-02 13:11:24 +01:00
croneter
089294681e Fix rare KeyError when using PKC widgets 2021-01-02 11:54:53 +01:00
croneter
a0280fdbd3 Fix suspension of artwork caching 2021-01-02 10:49:09 +01:00
croneter
e2ebe98fde
Update README.md 2021-01-02 10:29:08 +01:00
croneter
e60816c022
Merge pull request #1255 from croneter/readme
Update readme
2021-01-02 10:27:47 +01:00
croneter
fb53ba3a0a Update readme 2021-01-02 10:26:56 +01:00
croneter
e0a27bdb4e
Merge pull request #1251 from croneter/version-bump
Beta version bump 2.12.5
2020-12-18 15:54:23 +01:00
croneter
7a7ead863d
Merge pull request #1250 from croneter/fix-fanart-matching
Greatly improve matching logic for The Movie Database if Plex does not provide an appropriate id
2020-12-18 15:54:09 +01:00
croneter
56516d3e1c
Merge pull request #1249 from croneter/fix-playlist-keyerror
Fix rare KeyError: None when trying to sync playlists
2020-12-18 15:53:55 +01:00
croneter
681179683f
Merge pull request #1248 from croneter/fix-transcoding-options
Fix high transcoding resolutions not being available for Win10
2020-12-18 15:53:43 +01:00
croneter
690b8c1c94
Merge pull request #1247 from croneter/fix_keyerror
Fix rare playback progress report failing and KeyError: u'containerKey'
2020-12-18 15:53:30 +01:00
croneter
e5e92b851a
Merge pull request #1237 from croneter/fix_typeerror
Fix TypeError when canceling Plex sync section dialog
2020-12-18 15:53:12 +01:00
croneter
4a4aecd669 Beta version bump 2.12.5 2020-12-18 15:52:02 +01:00
croneter
c753d97d3f Greatly improve matching logic for tmdb if Plex does not provide id 2020-12-18 15:35:20 +01:00
croneter
1207ab485c Fix rare KeyError: None when trying to sync the playlist 2020-12-17 17:33:55 +01:00
croneter
71ebdc1e90 Fix high transcoding resolutions not being available for Win10 2020-12-17 17:05:24 +01:00
croneter
4c2fe6dd59 Fix rare playback progress report failing and KeyError: u'containerKey' 2020-12-17 16:59:19 +01:00
croneter
e551a9451a Fix TypeError when canceling Plex sync section dialog 2020-11-06 15:57:24 +01:00
croneter
3fd9fc4e3f
Merge pull request #1235 from croneter/version-bump
Beta version bump 2.12.4
2020-11-05 16:17:11 +01:00
croneter
955a600356 Merge branch 'fix_freeze' into beta-version 2020-11-05 16:16:01 +01:00
croneter
8ae6a8df48 Merge branch 'beta-version' into fix_freeze 2020-11-05 16:15:39 +01:00
croneter
e66f492389
Merge pull request #1233 from croneter/add_new_plex_agent
Support metadata provider ids (e.g. for IMDB) for the new Plex Movie Agent
2020-11-05 16:07:01 +01:00
croneter
3f1f41f5b6 Beta version bump 2.12.4 2020-11-05 16:05:58 +01:00
croneter
5d67d4a602 Support metadata provider ids (e.g. IMDB) for new Plex Movie Agent 2020-11-05 15:47:04 +01:00
croneter
ca64d54b4e Fix freeze: Don't assign multiple sets/collections for a specific movie 2020-11-01 16:10:44 +01:00
croneter
27202d2ab2
Merge pull request #1218 from croneter/beta-version
Bump master
2020-09-19 20:49:44 +02:00
croneter
3f74113e02
Merge pull request #1217 from croneter/version-bump
Stable and beta version bump 2.12.3
2020-09-19 20:46:15 +02:00
croneter
385bddf2da
Merge pull request #1216 from croneter/update_readme
Update readme to reflect new installation method by URL
2020-09-19 20:45:47 +02:00
croneter
249c0993e2
Merge pull request #1215 from croneter/fix-subtitle-download
Fix playback failing due to caching of subtitles with non-ascii chars
2020-09-19 20:45:21 +02:00
croneter
b50414ebd4
Merge pull request #1214 from croneter/fix-ValueError
Fix ValueError: invalid literal for int() with base 10 during show sync
2020-09-19 20:44:44 +02:00
croneter
12b863a5ba
Merge pull request #1206 from croneter/fixUnboundLocalError
Fix UnboundLocalError when certain Plex sections are deleted or being un-synched
2020-09-19 20:43:51 +02:00
croneter
d192f924b5 Stable and beta version bump 2.12.3 2020-09-19 20:42:21 +02:00
croneter
548be35c10 Update readme to reflect new installation method 2020-09-19 20:37:23 +02:00
croneter
4f9f7bc7c9 Fix playback failing due to caching of subtitles with non-ascii chars 2020-09-19 15:21:02 +02:00
croneter
06cc2b6cde Fix ValueError: invalid literal for int() with base 10 during show sync 2020-09-19 14:40:00 +02:00
croneter
61e4056a13 Fix UnboundLocalError when certain Plex sections are deleted or un-synced 2020-07-29 17:42:04 +02:00
croneter
ba6c46afac
Merge pull request #1201 from croneter/beta-version
Bump master
2020-07-11 15:50:16 +02:00
croneter
096046347b
Merge pull request #1200 from croneter/version-bump
Stable and beta version bump 2.12.2
2020-07-11 15:49:33 +02:00
croneter
618c7388b4
Merge pull request #1192 from croneter/fix-dialog
Fix regression: sync dialog not showing up when it should
2020-07-11 15:49:18 +02:00
croneter
2f9ae3c21c Stable and beta version bump 2.12.2 2020-07-11 15:43:56 +02:00
croneter
ccb7fa3e44 Fix regression: sync dialog not showing up when it should 2020-06-09 17:09:12 +02:00
croneter
4dba45f32a
Merge pull request #1189 from croneter/version-bump
Beta version bump 2.12.1
2020-06-09 12:07:23 +02:00
croneter
07e13e0985
Merge pull request #1188 from croneter/fix-dialog
Fix sync dialog showing in certain cases even though user opted out
2020-06-09 12:07:09 +02:00
croneter
f1a4ef35c5
Merge pull request #1187 from croneter/fix-content-type
Fix Kodi content type for images/photos
2020-06-09 12:06:54 +02:00
croneter
20c7ca0d05
Merge pull request #1186 from croneter/fix-spaces
Revert "Don't allow spaces in devicename"
2020-06-09 12:06:40 +02:00
croneter
9c1a753fa9
Merge pull request #1185 from croneter/fix-users
Fix PKC shutdown on Kodi profile switch
2020-06-09 12:06:26 +02:00
croneter
5b53feb1b4
Merge pull request #1184 from geropan/custom-safe-char-url
Added support for custom set of safe characters when escaping paths
2020-06-09 12:06:09 +02:00
croneter
92411bcb7b Fix sync dialog showing in certain cases even though user opted out 2020-06-09 12:04:48 +02:00
croneter
b3e41555ee Beta version bump 2.12.1 2020-06-09 11:59:50 +02:00
croneter
9d97d2b788 Fix Kodi content type for images/photos 2020-06-09 11:25:52 +02:00
croneter
075b28aa51 Revert "Don't allow spaces in devicename"
This reverts commit 2e5249ca4f.
2020-06-09 09:57:21 +02:00
croneter
f747086957 Migration: make sure user needs to sign in again after PKC update 2020-06-09 09:54:47 +02:00
croneter
da671c8ee5 Do not instantiate xbmc.Monitor() if possible (guess this leads to issues if we should shut down) 2020-06-09 09:37:18 +02:00
croneter
97c3239657 Explicitly delete xbmc.Monitor() and xbmc.Player() 2020-06-09 09:36:34 +02:00
croneter
28500d2cdf Correctly detect PKC shutdown and ensure that a PKC instance is always running (guess invoking xbmc.Monitor() while we should shut down did not help) 2020-06-09 09:36:10 +02:00
geropan
97078fda2c Addressed line too long issues. 2020-06-07 18:58:46 +01:00
geropan
2ce1a6e639 Added support for custom set of safe characters (configured via addon settings) when escaping paths using direct paths for http(s), dav(s) and (s)ftp urls. 2020-06-07 18:27:20 +01:00
croneter
038477fa77
Merge pull request #1174 from croneter/version-bump
Beta version bump 2.12.0
2020-05-07 14:07:18 +02:00
croneter
50888c445c
Merge pull request #1173 from croneter/fix-websocket
Fix websocket threads; enable PKC background sync for all Plex Home users!
2020-05-07 14:06:54 +02:00
croneter
c7480339cd
Merge pull request #1169 from croneter/fix-external-player
Fix PKC incorrectly marking a video as unwatched if an external player has been used
2020-05-07 14:06:38 +02:00
croneter
8e05ace380
Merge pull request #1168 from croneter/update-translations
Update translations
2020-05-07 14:05:53 +02:00
croneter
7937757de6 Beta version bump 2.12.0 2020-05-07 14:04:54 +02:00
croneter
e9218bf311 Enable PKC background sync for all Plex Home users! 2020-05-07 13:56:13 +02:00
croneter
c34f43cead Fix websocket threads not getting suspended correctly 2020-05-07 13:56:13 +02:00
croneter
5ffcd5782d Optimize some websocket code 2020-05-07 13:56:13 +02:00
croneter
54a147da41 Revert "Rewire suspension of waking up of websocket threads"
This reverts commit dccd3e512b.
2020-05-07 07:27:30 +02:00
croneter
dccd3e512b Rewire suspension of waking up of websocket threads 2020-05-07 07:26:03 +02:00
croneter
92a28b6eda Replace depricated xbmc.abortRequested 2020-05-05 18:18:34 +02:00
croneter
188dcf2cc1 Fix PKC incorrectly watching a video as unwatched if an external player was used 2020-05-03 09:36:01 +02:00
croneter
7ff40ee9cc Update translations 2020-05-02 13:20:31 +02:00
croneter
941ac4ef3b
Merge pull request #1158 from croneter/beta-version
Bump master
2020-03-25 16:40:25 +01:00
croneter
d9d89f3e6c
Merge pull request #1157 from croneter/version-bump
Stable and beta version bump 2.11.7
2020-03-25 16:39:45 +01:00
croneter
c48b6f48a8
Merge pull request #1156 from croneter/fix-crash
Fix PKC crashing on devices running Microsoft UWP, e.g. XBox
2020-03-25 16:39:22 +01:00
croneter
7c215e73d7 Stable and beta version bump 2.11.7 2020-03-25 16:29:51 +01:00
croneter
791a31bb65 Fix PKC crashing on devices running Microsoft UWP, e.g. XBox 2020-03-25 16:28:20 +01:00
croneter
493ac7f49a
Merge pull request #1150 from croneter/beta-version
Bump master
2020-03-21 14:31:53 +01:00
croneter
e3d4a7f7ca
Merge pull request #1149 from croneter/version-bump
Stable and beta version bump 2.11.6
2020-03-21 14:31:23 +01:00
croneter
0a9ac40cf0
Merge pull request #1148 from croneter/fix-queue
Fix rare sync crash when queue was full
2020-03-21 14:31:09 +01:00
croneter
33b6358133
Merge pull request #1146 from croneter/fix-setting
Set "Auto-adjust transcoding quality" to false by default
2020-03-21 14:30:56 +01:00
croneter
7bb1a895e8 Stable and beta version bump 2.11.6 2020-03-21 14:28:11 +01:00
croneter
db48ffb419 Fix rare sync crash when queue was full 2020-03-21 14:22:43 +01:00
croneter
1ff487d649 Set "Auto-adjust transcoding quality" to false by default 2020-03-19 07:49:47 +01:00
croneter
d8dc959879
Merge pull request #1144 from croneter/beta-version
Bump master
2020-03-13 07:53:24 +01:00
croneter
b40b03efc5
Merge pull request #1143 from croneter/version-bump
Stable and beta version bump 2.11.5
2020-03-13 07:52:53 +01:00
croneter
d1c6807ce6 Stable and beta version bump 2.11.5 2020-03-13 07:33:49 +01:00
croneter
04b5fa7c43
Merge pull request #1141 from croneter/version-bump
Beta version bump 2.11.4
2020-03-03 11:50:08 +01:00
croneter
7e3dcbe332
Merge pull request #1140 from croneter/fix-play
Fix another TypeError: 'NoneType' object has no attribute '__getitem__', e.g. when trying to play trailers
2020-03-03 11:49:55 +01:00
croneter
5459f6ce9a Beta version bump 2.11.4 2020-03-03 11:46:47 +01:00
croneter
403c34826c Fix another TypeError: 'NoneType' object has no attribute '__getitem__' 2020-03-03 11:34:54 +01:00
croneter
166bf40696
Merge pull request #1138 from croneter/version-bump
Beta version bump 2.11.3
2020-03-01 13:52:48 +01:00
croneter
7ede0566c8
Merge pull request #1137 from croneter/fix-album
Fix TypeError: 'NoneType' object has no attribute '__getitem__', e.g. when displaying albums
2020-03-01 13:52:32 +01:00
croneter
4bc4400100 Beta version bump 2.11.3 2020-03-01 13:50:44 +01:00
croneter
cd6a0f6fe4 Fix TypeError: 'NoneType' object has no attribute '__getitem__' 2020-03-01 13:41:23 +01:00
croneter
10fd6363b4
Merge pull request #1135 from croneter/version-bump
Beta version bump 2.11.2
2020-02-27 17:16:25 +01:00
croneter
0f90abab59
Merge pull request #1134 from croneter/fix-music-playlists
Refactor direct and add-on paths. Enables use of Plex music playlists synched to Kodi
2020-02-27 17:16:09 +01:00
croneter
f573a29d37 Ensure we resync all Plex playlists to Kodi when updating PKC 2020-02-27 16:38:53 +01:00
croneter
2f34ece3f5 Beta version bump 2.11.2 2020-02-27 16:33:25 +01:00
croneter
25f972f30f Refactor direct and add-on paths. Enables use of Plex music playlists synched to Kodi 2020-02-27 16:29:08 +01:00
croneter
6c9870f581
Merge pull request #1132 from croneter/version-bump
Beta version bump 2.11.1
2020-02-25 18:22:05 +01:00
croneter
5aceb223ee
Merge pull request #1131 from croneter/fix-playback
Rewire the set-up of audio and subtitle streams, esp. before starting a transcoding session. Fixes playback not starting at all
2020-02-25 18:21:20 +01:00
croneter
6a7ca3c4d1 Rewire the set-up of audio and subtitle streams, esp. before starting a transcoding session 2020-02-25 18:19:08 +01:00
croneter
f524674b68 Increase logging 2020-02-25 18:19:08 +01:00
croneter
cc44c72cd6 Increase logging 2020-02-25 18:19:08 +01:00
croneter
8bc9ff974f Beta version bump 2.11.1 2020-02-25 18:16:53 +01:00
croneter
3196bfce64 Merge branch 'master' of https://github.com/croneter/PlexKodiConnect into beta-version 2020-02-25 17:37:43 +01:00
croneter
21e9e460a8
Merge pull request #1127 from croneter/fix-subs
Fix regression: burn-in subtitles picking up the last user setting instead of the current one
2020-02-23 17:21:37 +01:00
croneter
41aef50463 Fix regression: burn-in subtitles picking up the last user setting instead of the current one 2020-02-23 17:21:09 +01:00
croneter
781426ba36
Merge pull request #1126 from croneter/version-bump
Beta version bump 2.11.0
2020-02-23 16:35:06 +01:00
croneter
872c313092
Merge pull request #1125 from croneter/fix-sub-dialog
When transcoding, only let user choose to burn-in subtitles that can't be displayed otherwise by Kodi
2020-02-23 16:34:41 +01:00
croneter
87cff3557d
Merge pull request #1124 from croneter/add-info
Ensure that our only video transcoding target is h264
2020-02-23 16:34:17 +01:00
croneter
7a0cad9734
Merge pull request #1123 from croneter/fix-sub-size
Fix adjusted subtitle size not working when burning in subtitles
2020-02-23 16:33:53 +01:00
croneter
98f983a830 Fix adjusted subtitle size not working when burning in subtitles 2020-02-23 16:30:25 +01:00
croneter
a1d32447ba
Merge pull request #1122 from croneter/fix-transcoding
Fix PKC not burning in (and thus not showing) subtitles when transcoding
2020-02-23 16:27:51 +01:00
croneter
a2197eb8a6
Merge pull request #1121 from croneter/fix-connection
Improve PKC automatically connecting to local PMS
2020-02-23 16:27:30 +01:00
croneter
4f4b89ca77 Beta version bump 2.11.0 2020-02-23 15:54:11 +01:00
croneter
9abca33a53 Update translations 2020-02-23 15:50:17 +01:00
croneter
1021c47b04 When transcoding, only let user choose to burn-in subtitles that can't be displayed otherwise by Kodi 2020-02-23 15:31:37 +01:00
croneter
690d0ce459 Don't transcode subtitles to .ass when burning in 2020-02-23 14:45:10 +01:00
croneter
f2fa3bfc41 Ensure that our only video transcoding target is h264 2020-02-23 14:43:03 +01:00
croneter
ddf4999caa Fix PKC not burning in (and thus not showing) subtitles when transcoding 2020-02-23 14:39:13 +01:00
croneter
cad5923546 Improve logic to connect to local PMS via plex.direct redirect 2020-02-21 09:12:59 +01:00
croneter
83598ff3f1 Fix PKC auto-connecting to a local PMS 2020-02-21 08:40:41 +01:00
croneter
a1bda39e9d Correctly detect whether we can use SSL to connect to the PMS 2020-02-21 08:22:09 +01:00
croneter
9c67283085
Merge pull request #1120 from croneter/beta-version
Bump master
2020-02-19 15:53:31 +01:00
croneter
310ae54e7b
Merge pull request #1119 from croneter/version-bump
Stable and beta version bump 2.10.12
2020-02-19 15:52:19 +01:00
croneter
0f914c52ab Stable and beta version bump 2.10.12 2020-02-19 15:51:11 +01:00
croneter
238ce2557c
Merge pull request #1118 from croneter/version-bump
Beta version bump 2.10.11
2020-02-17 19:20:10 +01:00
croneter
15241aab5d
Merge pull request #1117 from croneter/fix-queue
Fix yet another rare but annoying bug where PKC becomes unresponsive during sync
2020-02-17 19:19:47 +01:00
croneter
94b4ed52d6 Beta version bump 2.10.11 2020-02-17 19:18:15 +01:00
croneter
a67d39609e Get rid of obsolete code 2020-02-17 19:15:06 +01:00
croneter
64af58172b Fix yet another rare but annoying bug where PKC becomes unresponsive during sync 2020-02-17 19:15:06 +01:00
croneter
78c8ff73f2
Merge pull request #1116 from croneter/version-bump
Beta version bump 2.10.10
2020-02-15 18:46:16 +01:00
croneter
b56d67d3fa
Merge pull request #1115 from croneter/fix-websockets
Fix PKC background sync not working in some cases
2020-02-15 18:45:59 +01:00
croneter
fbf484cde4 Beta version bump 2.10.10 2020-02-15 18:45:06 +01:00
croneter
9952a9b44a
Merge pull request #1114 from croneter/fix-queue
Fix rare but annoying bug where PKC becomes unresponsive during sync
2020-02-15 18:42:58 +01:00
croneter
8a65a86cb2 Beta version bump 2.10.10 2020-02-15 18:42:26 +01:00
croneter
6f553e5c94 Fix PKC background sync not working in some cases 2020-02-15 18:40:14 +01:00
croneter
51d1538f95 Rewire the ProcessingQueue to ensure that we can exhaust it completely and don't get stuck 2020-02-15 17:47:17 +01:00
croneter
73ffb706f8 Make sure we're receiving valid item from the processing queue in case we should be aborting sync 2020-02-15 17:47:17 +01:00
croneter
a4a0b075bf Increase logging for the number of items we actually process 2020-02-15 17:47:17 +01:00
croneter
9a0ce533ee Rename method 2020-02-15 17:47:17 +01:00
croneter
ddd356deda Refactor code 2020-02-15 17:47:16 +01:00
croneter
b69070275f Make sure OrdererQueue returns the correct queue size 2020-02-15 17:47:16 +01:00
croneter
d116bbdfe9 Rename method 2020-02-14 13:40:46 +01:00
croneter
9b0075d6bb
Merge pull request #1109 from croneter/version-bump
Beta version bump 2.10.9
2020-02-02 14:19:12 +01:00
croneter
31323665e4
Merge pull request #1108 from croneter/search
Other Kodi add-ons can now search for Plex items using plugin://plugin.video.plexkodiconnect?mode=search&query=<your search string here>
2020-02-02 14:18:58 +01:00
croneter
8219932245 Beta version bump 2.10.9 2020-02-02 14:18:17 +01:00
croneter
25172c2f57 Other Kodi add-ons can now search for Plex items using plugin://plugin.video.plexkodiconnect?mode=search&query=<your search string here> 2020-02-02 14:09:10 +01:00
croneter
e55f16f61d
Merge pull request #1092 from croneter/version-bump
Beta version bump 2.10.8
2020-01-31 21:59:52 +01:00
croneter
e8242e0bcf
Merge pull request #1107 from croneter/fix-replace
Fix DirectPaths when a video's folder name is identical to a video's filename (you will need to manually reset the Kodi database)
2020-01-31 21:59:30 +01:00
croneter
2d20f0436e Beta version bump 2.10.8 2020-01-31 21:58:20 +01:00
croneter
23ac39a860 Fix DirectPaths when a video's folder name is identical to a video's filename (you will need to manually reset the Kodi database) 2020-01-31 21:12:48 +01:00
croneter
4a95b1007b
Merge pull request #1090 from croneter/improve-threader
Improve thread pool management to render PKC snappier
2019-12-20 14:18:23 +01:00
croneter
0255551ea9 Don't spin up 2 separate threads but use the thread pool 2019-12-20 14:17:49 +01:00
croneter
fe857cb609 Improve thread pool management to render PKC snappier 2019-12-20 14:17:49 +01:00
croneter
15371f35ec
Merge pull request #1089 from croneter/fix-broken-pipe
Attempt to fix broken pipe error
2019-12-20 14:16:35 +01:00
croneter
a1e6cdcf29 Attempt to fix broken pipe error 2019-12-15 07:36:50 +01:00
croneter
9328ebeecb
Merge pull request #1087 from croneter/version-bump
Beta version bump 2.10.7
2019-12-13 16:55:25 +01:00
croneter
e8d601d7d7
Merge pull request #1086 from croneter/fix-thread
Fix PKC becoming unresponsive e.g. when switching the PMS
2019-12-13 16:55:08 +01:00
croneter
fd80bc9cf3
Merge pull request #1085 from croneter/optimize-db
Optimize the new sync process and fix some bugs that were introduced
2019-12-13 16:54:48 +01:00
croneter
d84b7ccbe3
Merge pull request #1084 from croneter/fix-importerror
Fix PKC not starting up on iOS
2019-12-13 16:53:44 +01:00
croneter
8502c00d89 Beta version bump 2.10.7 2019-12-13 14:02:22 +01:00
croneter
b611a66ff5 Fix sync getting stuck 2019-12-13 14:00:46 +01:00
croneter
58a86d34f1 Clarify class description 2019-12-13 14:00:46 +01:00
croneter
6510d5e399 Fix display of item numbers during playstate sync 2019-12-13 14:00:46 +01:00
croneter
b55b22efb0 Clarify variables 2019-12-13 14:00:46 +01:00
croneter
136af95351 Speed up and simplify sync of playstates 2019-12-13 14:00:40 +01:00
croneter
654748218e Get section iterators in a dedicated thread to gain speed 2019-12-13 14:00:40 +01:00
croneter
0d537f108e Lower timeout for a DB connection from 30s to 10s 2019-12-13 14:00:40 +01:00
croneter
a715b3a473 raise exception instead of returning None if PKC needs to exit and we're trying to connect to a DB 2019-12-13 14:00:40 +01:00
croneter
b4e132af85 Optimize code 2019-12-13 14:00:40 +01:00
croneter
6d39adbd8c Use sqlite isolation_level=None in order to use autocommit mode and thus avoid sqlite auto-committing 2019-12-13 14:00:40 +01:00
croneter
70b7a44514 Avoid duplicate code 2019-12-13 14:00:39 +01:00
croneter
3000bfcd7d Always use sqlite WAL mode (did not switch back to normal journal mode automatically anyway) 2019-12-13 14:00:39 +01:00
croneter
9182e0ad76 Fix PKC becoming unresponsive e.g. when switching the PMS 2019-12-13 07:38:52 +01:00
croneter
ed3301a523 Fix PKC not starting up on iOS 2019-12-10 08:15:10 +01:00
croneter
d4d7c0f98c
Merge pull request #1083 from croneter/version-bump
Beta version bump 2.10.6
2019-12-08 16:48:45 +01:00
croneter
baa33f19b1
Merge pull request #1082 from croneter/fix-operationalerror
Fix OperationalError when starting with a fresh PKC installation
2019-12-08 16:48:27 +01:00
croneter
59424b2a7c
Merge pull request #1081 from croneter/fix-user
Fix AttributeError if user enters an invalid pin code
2019-12-08 16:48:12 +01:00
croneter
3fa067aca6
Merge pull request #1080 from croneter/fix-indexerror
Fix IndexError
2019-12-08 16:47:52 +01:00
croneter
0c337d8aae Beta version bump 2.10.6 2019-12-08 16:47:00 +01:00
croneter
80181873d1 Fix OperationalError when starting with a fresh PKC installation 2019-12-08 16:41:43 +01:00
croneter
f9755cc39c Fix AttributeError if user enters an invalid pin code 2019-12-08 16:33:08 +01:00
croneter
ab998f7941 Fix IndexError 2019-12-08 16:24:14 +01:00
croneter
9ab35b0a49
Merge pull request #1076 from croneter/version-bump
Beta version bump 2.10.5
2019-12-08 10:32:20 +01:00
croneter
4ebe11fcc4
Merge pull request #1079 from croneter/wal
Don't use WAL mode for sqlite connections, it is not making any difference
2019-12-08 10:32:03 +01:00
croneter
c3749c0bd2
Merge pull request #1078 from croneter/optimize-db
Optimize adding values to Kodi databases by not using sqlite COALESCE command
2019-12-08 10:31:47 +01:00
croneter
c3bad7c954
Merge pull request #1075 from croneter/improve-sync
Rewire library sync to speed it up and fix sync getting stuck in rare cases
2019-12-08 10:31:13 +01:00
croneter
2f1cae5026 Beta version bump 2.10.5 2019-12-08 10:29:45 +01:00
croneter
9080ca89b9 Don't use WAL mode for sqlite connections, it is not making any difference 2019-12-08 10:23:50 +01:00
croneter
e257e5426e Optimize adding values to Kodi databases by not using sqlite COALESCE command 2019-12-08 10:23:01 +01:00
croneter
2744b9da7e Copy entire plex.db to avoid db-locks entirely 2019-12-08 10:09:57 +01:00
croneter
a87dfa0a7a Don't use a dedicated thread to get section generators 2019-12-08 10:09:57 +01:00
croneter
8f86f43a93 Rewire library sync to speed it up and fix sync getting stuck in rare cases 2019-12-08 10:09:56 +01:00
croneter
f4ea051c81
Merge pull request #1073 from croneter/fix-userswitch
Optimize threads by using events instead of a polling mechanism. Fixes PKC becoming unresponsive, e.g. when switching users
2019-11-30 16:27:06 +01:00
croneter
343fce2102
Merge pull request #1072 from croneter/fix-bool
Make sure bool is returned instead of an int
2019-11-30 16:26:50 +01:00
croneter
aad68340cf
Merge pull request #1069 from croneter/fix-operationalerror
Fix OperationalError when resetting PKC
2019-11-30 16:26:36 +01:00
croneter
71f5b7169b
Merge pull request #1068 from croneter/fix-itemtypes
Improve sync resiliance when certain items are not to be synced to Kodi or PKC skipped an item in the past
2019-11-30 16:26:23 +01:00
croneter
c85e1e2bd0 Optimize threads by using events instead of a polling mechanism. Fixes PKC become unresponsive, e.g. when switching users 2019-11-30 13:48:51 +01:00
croneter
e01e50a650 Make sure bool is returned instead of an int 2019-11-30 13:46:08 +01:00
croneter
56324b1e88 Fix OperationalError when resetting PKC 2019-11-25 08:03:06 +01:00
croneter
bafd3545f4 Improve sync resiliance when certain items are not to be synced to Kodi 2019-11-24 14:58:39 +01:00
croneter
0987b43095
Merge pull request #1066 from croneter/beta-version
Bump master
2019-11-15 13:53:11 +01:00
croneter
764f132f66
Merge pull request #1065 from croneter/version-bump
Stable and beta version bump 2.10.4
2019-11-15 13:49:12 +01:00
croneter
b9aaf92aed
Merge pull request #1064 from croneter/optimize
Fix to correctly wipe Kodi databases
2019-11-15 13:48:36 +01:00
croneter
d050c5451d Stable and beta version bump 2.10.4 2019-11-15 13:40:34 +01:00
croneter
e52b67c3a9 Fix to correctly wipe Kodi databases 2019-11-15 13:38:41 +01:00
croneter
19a964ccb2
Merge pull request #1061 from croneter/version-bump
Beta version bump 2.10.3
2019-11-14 17:37:40 +01:00
croneter
2446cdc41a
Merge pull request #1062 from croneter/faster-server-list
Check faster for available Plex Media Server to connect to
2019-11-14 17:36:47 +01:00
croneter
c7cd15a670 Beta version bump 2.10.3 2019-11-14 17:35:50 +01:00
croneter
999743c6c1 Check faster for available Plex Media Server to connect to 2019-11-14 17:34:50 +01:00
croneter
5273e874e0
Merge pull request #1060 from croneter/fix-keyerror
Fix KeyError when using Plex search capabilities
2019-11-14 17:21:42 +01:00
croneter
aff0fd7a5f
Merge pull request #1059 from croneter/fix-regex
Fix a couple of issues with music when using direct paths: correctly escape music paths for Kodi regex matching
2019-11-14 17:21:13 +01:00
croneter
8e72033aef
Merge pull request #1058 from croneter/fix-locked-db
Fix database being locked in rare cases
2019-11-14 17:20:48 +01:00
croneter
24ec8dd8e4
Merge pull request #1057 from croneter/increase-batch
Increase batch size for library sync from 500 to 2000 to increase sync speed
2019-11-14 17:20:24 +01:00
croneter
1654c3175e
Merge pull request #1056 from croneter/optimize
Optimize some code
2019-11-14 17:20:06 +01:00
croneter
ffeb79e4b5
Merge pull request #1054 from croneter/fix-album
Fix Recently Added Albums sort order (you will have to reset the Kodi database manually)
2019-11-14 17:19:37 +01:00
croneter
2359430260 Fix KeyError when using Plex search capabilities 2019-11-14 16:11:07 +01:00
croneter
e17d9bf1dd Fix a couple of issues with music when using direct paths: escape the path for Kodi regex matching 2019-11-14 15:44:29 +01:00
croneter
3b8f712289 Fix database being locked in rare cases 2019-11-14 14:39:07 +01:00
croneter
2fcbc1f9b7 Optimize code 2019-11-13 17:55:44 +01:00
croneter
7a8cec5968 Increase batch size for library sync from 500 to 2000 to increase sync speed 2019-11-13 17:53:46 +01:00
croneter
f67ff2f136 Fix recently added albums sort order (you will have to reset the Kodi database manually) 2019-11-13 17:49:16 +01:00
croneter
6652f764b0
Merge pull request #1052 from croneter/beta-version
Bump master
2019-11-07 07:48:15 +01:00
croneter
938b82da9c
Merge pull request #1051 from croneter/version-bump
Stable and beta version bump 2.10.2
2019-11-07 07:47:42 +01:00
croneter
a027fe96ed Stable and beta version bump 2.10.2 2019-11-07 07:47:05 +01:00
croneter
1ae4aa2185
Merge pull request #1050 from croneter/update-translations
Update translations
2019-11-07 07:45:42 +01:00
croneter
1523ab1166 Update translations 2019-11-07 07:14:39 +01:00
croneter
5dc7b96072
Merge pull request #1048 from croneter/fix-transcoding
Fix transcoding quality degenerating quickly while playing with a new setting to deactivate auto quality for transcoding (applicable e.g. for Chromecast)
2019-11-07 07:06:26 +01:00
croneter
4947b561ef
Merge pull request #1047 from croneter/fix-resume
Fix Kodi playback jumping to the beginning of a video that just started
2019-11-07 07:06:11 +01:00
croneter
9e930f09ca Fix transcoding quality degenerating quickly while playing with a new setting to deactivate auto quality for transcoding 2019-11-07 07:00:46 +01:00
croneter
4f59a1e2a9 Fix Kodi playback jumping to the beginning of a video that just started 2019-11-06 18:39:19 +01:00
croneter
52aaa80714
Merge pull request #1043 from croneter/beta-version
Bump master
2019-11-03 14:37:24 +01:00
croneter
c7da016c85
Merge pull request #1042 from croneter/version-bump
Stable and beta version bump 2.10.1
2019-11-03 14:35:19 +01:00
croneter
07c8a1c67d
Merge pull request #1039 from croneter/fix-external-player
Fix resume when using an external player
2019-11-03 14:34:30 +01:00
croneter
cca78faa42
Merge pull request #1041 from croneter/fix-resume
Fix resume for Kodi on low powered devices, e.g. Raspberry Pi
2019-11-03 14:34:11 +01:00
croneter
940d11a00e
Merge pull request #1040 from croneter/fix-dependencies
Fix UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal
2019-11-03 14:33:44 +01:00
croneter
b6d6482c4a Fix resume when using an external player 2019-11-03 14:32:51 +01:00
croneter
4b46b11a59 Stable and beta version bump 2.10.1 2019-11-03 14:28:40 +01:00
croneter
ab77ddbe8b Fix resume for Kodi on low powered devices, e.g. Raspberry Pi 2019-11-03 14:24:20 +01:00
croneter
e9824158ef Fix UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal 2019-11-02 17:14:46 +01:00
croneter
6376fecbf7
Merge pull request #1038 from croneter/beta-version
Bump master
2019-11-02 12:14:35 +01:00
croneter
bae4178c5b
Merge pull request #1037 from croneter/version-bump
Stable and beta version bump 2.10.0
2019-11-02 12:14:03 +01:00
croneter
94120ee233
Merge pull request #1036 from croneter/fix-code
Get rid of some obsolete code for the ContextMonitor we dropped
2019-11-02 12:12:55 +01:00
croneter
d70f27df2c Stable and beta version bump 2.10.0 2019-11-02 12:12:31 +01:00
croneter
a9b5ba4162 Get rid of some obsolete code for the ContextMonitor we dropped 2019-11-01 13:40:45 +01:00
croneter
af06fdc84b
Merge pull request #1035 from croneter/version-bump
Beta version bump 2.9.14
2019-11-01 13:35:08 +01:00
croneter
38636b6943
Merge pull request #1034 from croneter/fix-filetable
Optimize clean-up of file table in the Kodi video database after stopping playback
2019-11-01 13:34:50 +01:00
croneter
14113d0ff7
Merge pull request #1033 from croneter/optimize
Get rid of some obsolete imports
2019-11-01 13:34:37 +01:00
croneter
ca1e0d7b3d
Merge pull request #1032 from croneter/optimize-code
Get rid of ContextMonitor and the dedicated Python thread - with new resume mechanics, this is not needed anymore
2019-11-01 13:34:19 +01:00
croneter
dc12c89385
Merge pull request #1031 from croneter/fix-resume
Fix resume when starting playback via PMS or when force transcoding
2019-11-01 13:33:54 +01:00
croneter
f56004f92a Beta version bump 2.9.14 2019-11-01 13:32:54 +01:00
croneter
db80f2b69a Optimize clean-up of file table in the Kodi video database 2019-11-01 13:15:34 +01:00
croneter
8abbe74145 Get rid of some obsolete imports 2019-11-01 13:07:15 +01:00
croneter
5e3f3daf90 Get rid of ContextMonitor and the dedicated Python thread - with new resume mechanics, this is not needed anymore 2019-11-01 13:00:34 +01:00
croneter
da90e61ca8 Fix resume when starting playback via PMS or when force transcoding 2019-11-01 12:54:49 +01:00
croneter
2ba406bd4e
Merge pull request #1030 from croneter/version-bump
Beta version bump 2.9.13
2019-10-31 20:14:24 +01:00
croneter
389752b041
Merge pull request #1029 from croneter/fix-resume
Fix PKC resuming instead of playing from the beginning
2019-10-31 20:14:09 +01:00
croneter
2a11b37857 Beta version bump 2.9.13 2019-10-31 20:11:27 +01:00
croneter
e73c14bcf4 Fix PKC resuming instead of playing from the beginning 2019-10-31 20:09:31 +01:00
croneter
71935bf6ac
Merge pull request #1028 from croneter/version-bump
Beta version bump 2.9.12
2019-10-31 13:39:37 +01:00
croneter
cdb8141adf
Merge pull request #1027 from croneter/language
Always use the current Kodi language when communicating with the PMS (restart Kodi when changing the language!)
2019-10-31 13:39:18 +01:00
croneter
1618d96699
Merge pull request #1026 from croneter/search
Support Plex search across all media and Plex Media Servers: Navigate to the PlexKodiConnect Add-on, then "Search"
2019-10-31 13:38:59 +01:00
croneter
8e8a42ad12
Merge pull request #1025 from croneter/fix-playlists
Fix PKC throwing error if m3u playlist contains resume information
2019-10-31 13:38:40 +01:00
croneter
ee5a71c5ce
Merge pull request #1024 from croneter/fix-crash
Fix Kodi crashing when casting from e.g. Plex Web or Plex for Windows
2019-10-31 13:38:23 +01:00
croneter
265b2dcf6e
Merge pull request #1021 from croneter/fix-resume
Fix resume not working in some cases
2019-10-31 13:37:56 +01:00
croneter
c063694999 Beta version bump 2.9.12 2019-10-31 13:09:43 +01:00
croneter
0a06a6ad78 Always use the current Kodi language when communicating with the PMS 2019-10-31 13:01:08 +01:00
croneter
845cfb44d5 Support Plex search across all media and Plex Media Servers 2019-10-31 12:51:21 +01:00
croneter
80d55a5388 Fix PKC throwing error if m3u playlist contains resume information 2019-10-31 07:47:34 +01:00
croneter
8830cf22db Fix Kodi crashing when casting from Plex Web 2019-10-30 17:53:40 +01:00
croneter
730ac203ad Remove hack to detect replaying of the last video - does not work anymore 2019-10-30 17:30:52 +01:00
croneter
9f2210a5e7 Rewire PKC resume mechanism 2019-10-30 17:30:52 +01:00
croneter
76e1b1c629 Fix resume when casting to PKC 2019-10-29 08:03:32 +01:00
croneter
ca52117c4f Fix resume not working in some cases 2019-10-25 14:09:05 +02:00
croneter
45cd1aa0fc
Merge pull request #1016 from croneter/beta-version
Bump master
2019-10-12 12:56:25 +02:00
croneter
2481606cd7
Merge pull request #1015 from croneter/version-bump
Stable and beta version bump 2.9.11
2019-10-12 12:55:34 +02:00
croneter
fbb0d1542a Stable and beta version bump 2.9.11 2019-10-12 12:54:45 +02:00
croneter
7a3db9ea2e
Merge pull request #1013 from croneter/version-bump
Beta version bump 2.9.10
2019-10-05 17:01:37 +02:00
croneter
feb73b7e47
Merge pull request #1012 from croneter/delete-subs
Delete temporary subtitles on playback stop
2019-10-05 17:01:10 +02:00
croneter
1fa1035a43 Delete temporary subtitles on playback stop 2019-10-05 17:00:39 +02:00
croneter
af23a4aabd
Merge pull request #1011 from croneter/fix-subtitle-naming
Improve subtitle naming
2019-10-05 16:58:41 +02:00
croneter
2506dbeb43
Merge pull request #1010 from croneter/fix-subtitles
Fix external subtitles not being available
2019-10-05 16:58:18 +02:00
croneter
a601311cd8
Merge pull request #1009 from croneter/fix-string
Fix a missleading string
2019-10-05 16:57:56 +02:00
croneter
8a3e580975
Merge pull request #1008 from croneter/fix-viewcount
Fix PKC increasing the Plex watch count by 2 instead of 1
2019-10-05 16:57:29 +02:00
croneter
8fc76386e2 Merge branch 'bernimoses-master' into beta-version 2019-10-05 16:55:41 +02:00
croneter
413e856a11 Beta version bump 2.9.10 2019-10-05 16:53:32 +02:00
croneter
a5ac528329 Improve subtitle naming 2019-10-05 16:39:01 +02:00
croneter
2f0767d086 Fix NameError when transcoding 2019-10-05 12:59:48 +02:00
croneter
b0fbb3ac09 Fix regression: labeling of playmethods 2019-10-05 12:43:12 +02:00
croneter
04272a9d3f Fix a missleading string 2019-10-05 12:04:48 +02:00
croneter
7ea9222e47 Fix PKC increasing the Plex watch-count by 2 instead of 1 2019-10-05 11:32:29 +02:00
croneter
d75e2a0109 Remember currently playing item directly 2019-10-05 11:31:44 +02:00
croneter
ae0e121b13 Optimize code 2019-10-05 10:46:33 +02:00
bernimoses
9933480f8d Add tmdb provider sync. 2019-10-03 22:17:00 +02:00
croneter
3208d1d71d
Merge pull request #1005 from croneter/beta-version
Bump master
2019-10-03 18:58:07 +02:00
croneter
21bf68e148
Merge pull request #1004 from croneter/version-bump
Stable and beta version bump 2.9.9
2019-10-03 18:57:27 +02:00
croneter
58111cf701 Stable and beta version bump 2.9.9 2019-10-03 18:56:24 +02:00
croneter
d4973d355a
Merge pull request #1002 from croneter/version-bump
Beta version bump 2.9.8
2019-09-29 18:51:11 +02:00
croneter
eef8a94b35
Merge pull request #1001 from croneter/fix-playerror
Fix Play Error in scenarios (older PMS version?) where posting playqueues using an uri `server://` is not possible and `library://` is necessary
2019-09-29 18:50:31 +02:00
croneter
211362aaf3
Merge pull request #998 from croneter/fix-attributeerror
Fix rare AttributeError on PKC startup when modifying advancedsettings.xml
2019-09-29 18:50:14 +02:00
croneter
a19b789c7e
Merge pull request #994 from croneter/update-translations
Update translations
2019-09-29 18:49:30 +02:00
croneter
6b911e53aa Beta version bump 2.9.8 2019-09-29 18:48:42 +02:00
croneter
5fb2279c53 Fix PlexKodiConnect Play error in rare scenarios (an older PMS version?), where posting playqueues using an uri server:// is not possible and library:// is necessary 2019-09-29 17:45:16 +02:00
croneter
1e60dd11df Fix rare AttributeError on PKC startup when modifying advancedsettings.xml 2019-09-24 08:16:04 +02:00
croneter
eb78087b12 Update translations 2019-09-22 13:45:56 +02:00
croneter
055fe9aaa7
Merge pull request #993 from croneter/version-bump
Beta version bump 2.9.7
2019-09-22 13:24:16 +02:00
croneter
a8ee68ca4c
Merge pull request #990 from croneter/fix-escape
Correctly escape URLs for Direct Paths
2019-09-22 13:23:41 +02:00
croneter
2e90a30dba
Merge pull request #988 from croneter/optimize-migration
Don't migrate PKC settings if we're dealing with a clean new PKC installation
2019-09-22 13:22:48 +02:00
croneter
801695fdd4 Don't migrate PKC settings if we're dealing with a fresh PKC installation 2019-09-22 13:17:10 +02:00
croneter
f8ec9bbf9e
Merge pull request #987 from croneter/fix-migration
Force-scan every single item in the library - seems like we could lose some recently added items otherwise when updating PKC
2019-09-22 13:15:19 +02:00
croneter
e26cf09fef Force a re-scan of every single Plex item - we might have lost some recently added 2019-09-22 13:14:05 +02:00
croneter
51a09ffb11
Merge pull request #991 from croneter/change-string
Update settings to inform user that reboot is necessary
2019-09-22 13:11:55 +02:00
croneter
a0c55d5b15
Merge pull request #992 from croneter/fix-obsolete
Remove some obsolete code
2019-09-22 13:11:25 +02:00
croneter
c9e3d79acd
Merge pull request #989 from croneter/fix-import
Remove obsolete import
2019-09-22 13:10:46 +02:00
croneter
9d3eebb8cd Beta version bump 2.9.7 2019-09-22 13:09:39 +02:00
croneter
5a7d997da2 Correctly escape URLs for Direct Paths 2019-09-22 12:52:32 +02:00
croneter
3ae447012a Remove some obsolete code 2019-09-16 07:13:33 +02:00
croneter
56494cff7c Update string to inform user that reboot is necessary 2019-09-16 06:55:31 +02:00
croneter
1d210c2fde Remove obsolete import 2019-09-10 17:59:10 +02:00
croneter
ddaa26c385
Merge pull request #986 from croneter/fix-resume
Fix resume when force-transcoding
2019-09-08 16:18:21 +02:00
croneter
5205964c60 Fix resume when force-transcoding 2019-09-08 16:17:33 +02:00
croneter
bf04a64ca5
Merge pull request #985 from croneter/version-bump
Beta version bump 2.9.6
2019-09-08 15:29:36 +02:00
croneter
52c1a0e47d
Merge pull request #975 from croneter/fix-play-decision
Rework logic for using direct paths, direct play, direct streaming and transcoding, using the PMS StreamingBrain
2019-09-08 15:28:29 +02:00
croneter
d3450b1266 Beta version bump 2.9.6 2019-09-08 15:28:07 +02:00
croneter
a1a174a9e1
Merge pull request #979 from croneter/fix-update
Only enforce advancedsettings.xml 'cleanonupdate' to be false for PKC add-on paths
2019-09-08 15:26:00 +02:00
croneter
fbb65913db
Merge pull request #978 from croneter/fix-sync-items
Force a full sync of all items after choosing a new PMS, changing a PMS' address and changing which Plex libraries to sync
2019-09-08 15:25:30 +02:00
croneter
58e26be021
Merge pull request #977 from croneter/fix-sync
Fix PKC background sync synching items to Kodi even though entire section should not be synched
2019-09-08 15:25:00 +02:00
croneter
f7d8bccca3
Merge pull request #969 from croneter/fix-comp
Never give up trying to connect to the PMS or Alexa using websockets
2019-09-08 15:24:01 +02:00
croneter
9b12647957 New setting to force transcode only 4K and above 2019-09-08 15:22:09 +02:00
croneter
7bd02861dc Allow for 4k transcoding and direct streaming 2019-09-08 15:22:09 +02:00
croneter
b5fec41448 Dont decide playback on optimizedForStreaming 2019-09-08 15:22:09 +02:00
croneter
e498736a96 Let PMS StreamingBrain decide on whether we need to force-transcode 2019-09-08 15:22:09 +02:00
croneter
69049c62f1 New setting to choose "Direct Streaming" 2019-09-08 15:22:09 +02:00
croneter
0765583dda
Merge pull request #984 from croneter/beta-version
Bump master
2019-09-08 15:10:48 +02:00
croneter
7bb469702a Fix wording: "direct path" is not "direct play" 2019-09-08 15:06:07 +02:00
croneter
816913031c Optimize code 2019-09-08 15:06:07 +02:00
croneter
fbad92a9b3
Merge pull request #983 from croneter/version-bump
Stable and beta version bump 2.9.5
2019-09-08 14:57:51 +02:00
croneter
7f1b9f4d5b Stable and beta version bump 2.9.5 2019-09-08 14:56:38 +02:00
croneter
080eef441e Only enforce advancedsettings.xml cleanonupdate to be false for PKC add-on paths 2019-08-29 10:59:50 +02:00
croneter
e009c371b2 Force a full sync of all items after choosing a new PMS, changing a PMS' address and changing which Plex libraries to sync 2019-08-29 10:32:59 +02:00
croneter
65a921c3cc Fix PKC background sync synching items to Kodi even though entire section should not be synched 2019-08-29 10:08:01 +02:00
croneter
228cdc437d Never give up trying to connect to the PMS or Alexa using websockets 2019-08-17 09:52:58 +02:00
croneter
fe0b224047
Merge pull request #968 from croneter/version-bump
Beta version bump 2.9.4
2019-08-10 13:28:49 +02:00
croneter
0541d38b3d
Merge pull request #967 from croneter/fix-racing
Fix playback report not working after having played a non-Plex video file
2019-08-10 13:28:34 +02:00
croneter
12fe644b85 Merge branch 'code-opt' into beta-version 2019-08-10 13:28:00 +02:00
croneter
f1aa42b957 Merge branch 'beta-version' into code-opt 2019-08-10 13:27:11 +02:00
croneter
ac633d99e5
Merge pull request #964 from croneter/fix-report
Change how items are added to Plex playqueues by using PMS machine identifier
2019-08-10 13:24:53 +02:00
croneter
cfe3f55234
Merge pull request #962 from croneter/fix-extras
Fix extras not playing when path substitution is enabled
2019-08-10 13:24:33 +02:00
croneter
7ff4baac3a
Merge pull request #960 from croneter/fix-attributeerror
Fix rare AttributeError when shutting down Kodi
2019-08-10 13:24:17 +02:00
croneter
c0f01db7c1
Merge pull request #959 from croneter/fix-companion
Fix Plex Companion device restarting playback when reconnecting to PKC
2019-08-10 13:23:49 +02:00
croneter
eac27032fa Beta version bump 2.9.4 2019-08-10 13:22:12 +02:00
croneter
80abe0b34b Fix playback report not working after having played a non-Plex video file 2019-08-10 12:01:23 +02:00
croneter
a4526080db Code optimizations for PlaylistItem 2019-08-09 20:44:42 +02:00
croneter
95b469efb5 Faster lookup of elements in our plex.db 2019-08-09 20:42:31 +02:00
croneter
24ebb38f74 Reuse existing code 2019-08-09 20:42:12 +02:00
croneter
4f6156cf30 Rename class to PlaylistItem 2019-08-09 20:41:51 +02:00
croneter
6b5750910c Optimize logging 2019-08-09 20:41:30 +02:00
croneter
0d7a1b3a9f Change way item is added to Plex playqueue by using PMS machine identifier 2019-08-09 17:06:14 +02:00
croneter
b7f13a8842 Fix extras not playing when path substitution is enabled 2019-08-09 08:16:17 +02:00
croneter
f5026b637d Fix rare AttributeError when Kodi exits 2019-08-08 19:54:39 +02:00
croneter
b1d59e65be Fix Plex Companion device restarting playback when reconnecting to PKC 2019-08-06 07:33:35 +02:00
croneter
b5e13d0ab6
Merge pull request #958 from croneter/beta-version
Bump master
2019-08-03 10:44:35 +02:00
croneter
9139669f44
Merge pull request #957 from croneter/version-bump
Stable and beta version bump 2.9.3
2019-08-03 10:44:09 +02:00
croneter
09989b814b Stable and beta version bump 2.9.3 2019-08-03 10:43:14 +02:00
croneter
09bdda94bc
Merge pull request #956 from croneter/update-version
Update changelog for version 2.9.2
2019-08-02 11:35:06 +02:00
croneter
d88b62ccd9 Update changelog for version 2.9.2 2019-08-02 11:34:07 +02:00
croneter
1dc458cca0
Merge pull request #955 from croneter/fix-playlists
Correctly detect whether we already synched a Kodi playlist
2019-08-02 11:33:03 +02:00
croneter
34e84cd037 Correctly detect whether we already synched a Kodi playlist 2019-08-02 11:32:03 +02:00
croneter
bf19a66394
Merge pull request #951 from croneter/version-bump
Beta version bump 2.9.2
2019-08-02 10:52:05 +02:00
croneter
d2b5bc8d23
Merge pull request #954 from croneter/fix-companion
Fix Plex Companion casting from iOS and Android
2019-08-02 10:51:49 +02:00
croneter
064e573af9
Merge pull request #953 from croneter/optimize-sync
Sync playlists immediately after synching new/changed items and show an info dialog
2019-08-02 10:51:28 +02:00
croneter
b5bd13f7bd
Merge pull request #952 from croneter/less-logging
Less logging when comparing PKC versions
2019-08-02 10:51:10 +02:00
croneter
335bfc34c5
Merge pull request #946 from croneter/faster-playlists
Faster sync of playlists
2019-08-02 10:50:55 +02:00
croneter
91da038413
Merge pull request #950 from croneter/fix-check
Remove obsolete check if path is indeed in unicode
2019-08-02 10:50:30 +02:00
croneter
2cee5512d9
Merge pull request #948 from croneter/new-function
Separate function to wipe all synched Plex playlists
2019-08-02 10:50:12 +02:00
croneter
46830d29a9
Merge pull request #949 from croneter/unicode
Add unicode representation to Playlist() class
2019-08-02 10:49:56 +02:00
croneter
b17e5c124a
Merge pull request #947 from croneter/fix-dot
Fix potential playlist sync issues if there is a dot in the playlist name
2019-08-02 10:49:39 +02:00
croneter
8560bf11a8 Beta version bump 2.9.2 2019-08-02 10:48:21 +02:00
croneter
bae923d34a Fix Plex Companion casting from iOS and Android 2019-08-02 10:46:04 +02:00
croneter
33ed1bed83 Sync playlists immediately after synching new/changed items and show pop-up info 2019-08-02 10:25:53 +02:00
croneter
bcc97df209 Less logging when comparing PKC versions 2019-08-02 10:17:02 +02:00
croneter
7d8802467f Ensure playlists are freshly synched on PKC version bump 2019-08-01 14:53:49 +02:00
croneter
26fa1ff909 Use file size and last modification time to compare Kodi playlist files instead of slow MD5 hash of file 2019-08-01 14:53:49 +02:00
croneter
dca1bb6835 Remove obsolete check 2019-08-01 14:51:45 +02:00
croneter
53f77a7a97 Add unicode representation to Playlist() class 2019-08-01 14:50:09 +02:00
croneter
a83cf93150 Separate function to wipe all synched Plex playlists 2019-08-01 14:48:42 +02:00
croneter
85e0909105 Fix potential playlist sync issues with dot in playlist name 2019-08-01 14:38:44 +02:00
croneter
ae7578eeb5
Merge pull request #943 from croneter/beta-version
Bump master
2019-07-27 12:32:05 +02:00
croneter
92a7fa7c7a
Merge pull request #942 from croneter/version-bump
Stable and beta version bump 2.9.1
2019-07-27 12:31:32 +02:00
croneter
3cdec739a6
Merge pull request #941 from croneter/fix-ondeck
Fix On Deck and Recently Added Episodes for shows not appending showname and season and episode number
2019-07-27 12:31:17 +02:00
croneter
335b994635 Stable and beta version bump 2.9.1 2019-07-27 12:28:51 +02:00
croneter
9b98ee9ec3 Fix On Deck for shows not appending showname and season and episode number 2019-07-27 12:08:42 +02:00
croneter
1ed8de2e0f
Merge pull request #937 from croneter/beta-version
Bump master
2019-07-21 13:07:41 +02:00
croneter
3e9abbfaaa
Merge pull request #935 from croneter/version-bump
Stable and beta version bump 2.9.0
2019-07-21 13:05:05 +02:00
croneter
1070f3ed1f Stable and beta version bump 2.9.0 2019-07-21 13:03:44 +02:00
croneter
640876f961
Merge pull request #936 from croneter/update-translations
Add new Lithuanian translations (thanks @egidusm)
2019-07-21 12:50:44 +02:00
croneter
10b01a7aec
Merge pull request #934 from croneter/fix-attributeerror
Fix AttributeError: 'NoneType' object has no attribute 'attrib' on playback startup
2019-07-21 12:50:15 +02:00
croneter
45ce7ae932 Add new Lithuanian translations (thanks @egidusm) 2019-07-21 12:45:52 +02:00
croneter
245dcb77bc Update addon.xml 2019-07-21 12:44:18 +02:00
croneter
4b598200b9 AttributeError: 'NoneType' object has no attribute 'attrib' on playback startup 2019-07-21 12:11:28 +02:00
croneter
140030df33
Merge pull request #931 from croneter/version-bump
Beta version bump 2.8.11
2019-07-14 13:01:15 +02:00
croneter
61f13f516b
Merge pull request #932 from croneter/fix-companion
Fix casting to PlexKodiConnect always starting the first episode
2019-07-14 13:00:52 +02:00
croneter
024bf31b83
Merge pull request #930 from croneter/upnext
Support for the Up Next Kodi add-on
2019-07-14 13:00:37 +02:00
croneter
a12ee6695e
Merge pull request #929 from croneter/switch-node
Rename video nodes for ondeck
2019-07-14 13:00:23 +02:00
croneter
116dd76475 Beta version bump 2.8.11 2019-07-14 12:58:44 +02:00
croneter
5438fe4388 Fix casting to PlexKodiConnect always starting the first episode 2019-07-14 12:57:20 +02:00
croneter
38977a8ca6 Support for the Upnext Kodi add-on 2019-07-14 12:09:25 +02:00
croneter
a43e0801b4 Rename video nodes for ondeck 2019-07-14 07:46:09 +02:00
croneter
f126c8005a
Merge pull request #928 from croneter/version-bump
Beta version bump 2.8.10
2019-07-13 16:29:05 +02:00
croneter
ac8a15f2b4
Merge pull request #927 from croneter/fix-update
Fix broken PKC update
2019-07-13 16:28:05 +02:00
croneter
cf295e5452 Beta version bump 2.8.10 2019-07-13 16:27:19 +02:00
croneter
814f431a11 Fix broken PKC update 2019-07-13 16:26:07 +02:00
croneter
e01f720e16
Merge pull request #926 from croneter/version-bump
Beta version bump 2.8.9
2019-07-13 16:16:46 +02:00
croneter
7c5c91b590
Merge pull request #925 from croneter/fix-directories
Provide more metadata for unsynced directory-like items like a tv show
2019-07-13 16:16:36 +02:00
croneter
6a5d655a50 Beta version bump 2.8.9 2019-07-13 15:49:10 +02:00
croneter
3436035530 Provide more metadata for unsynced directory-like items like a show 2019-07-13 14:49:59 +02:00
croneter
84d4e5aa99
Merge pull request #922 from croneter/fix-path
Fix 'Plex.nodes.<id>.path' not linking directly to entire library
2019-07-13 10:32:44 +02:00
croneter
1fcae8b6de
Merge pull request #921 from croneter/fix-node
Fix sections that are not synced not displaying menu but entire library
2019-07-13 10:32:31 +02:00
croneter
33d20f0d0b Fix Plex.nodes.<id>.path not linking directly to entire library 2019-07-09 07:54:17 +02:00
croneter
e718fd3276 Fix sections that are not synced not displaying correct menu 2019-07-09 07:37:18 +02:00
croneter
d912d8be96
Merge pull request #918 from croneter/version-bump
Beta version bump 2.8.8
2019-07-07 18:10:10 +02:00
croneter
76cbd27ae3
Merge pull request #919 from croneter/fix-actor-image
Fix missing cast artwork if an actor also acted as director or writer for another movie. You will have to manually reset the Kodi DB.
2019-07-07 18:09:38 +02:00
croneter
53139a7e45
Merge pull request #917 from croneter/fix-skin
Ensure correct Kodi Container.Type is set for PKC widgets
2019-07-07 18:09:21 +02:00
croneter
4f1cc5f15d Beta version bump 2.8.8 2019-07-07 18:08:28 +02:00
croneter
868cecfe35 Fix missing cast artwork if an actor acts as director or writer 2019-07-07 18:05:56 +02:00
croneter
a5aa0c2594 Reset PKC video library XMLs on PKC update 2019-07-06 21:22:12 +02:00
croneter
fc237383aa Ensure correct Container.Type is set for PKC widgets 2019-07-06 21:20:23 +02:00
croneter
befe090661
Merge pull request #916 from croneter/beta-version
Bump master
2019-06-29 16:55:04 +02:00
croneter
9a670f498c
Merge pull request #915 from croneter/version-bump
Stable and beta version bump 2.8.7
2019-06-29 16:54:29 +02:00
croneter
fb4de9fb92
Merge pull request #913 from croneter/fix-timezones
Fix PKC potentially marking a video as watched on startup; don't sync time by toggling a video watch status but use PMS epoch time
2019-06-29 16:54:11 +02:00
croneter
112d177c50 Stable and beta version bump 2.8.7 2019-06-29 16:52:50 +02:00
croneter
416c424f52 Fix PKC potentially marking a video as watched on startup; don't sync time with toggling video watch state but use PMS epoch time 2019-06-28 17:47:01 +02:00
croneter
8c60050bcd
Merge pull request #912 from croneter/beta-version
Bump master
2019-06-28 16:49:04 +02:00
croneter
ee1cbc9feb
Merge pull request #911 from croneter/version-bump
Stable and beta version bump 2.8.6
2019-06-28 16:48:18 +02:00
croneter
5466bb759c
Merge pull request #910 from croneter/fix-playlists
Fix PKC creating thousands of playlists if a single Kodi playlist wasn't unique
2019-06-28 16:47:34 +02:00
croneter
1bd29587e1
Merge pull request #908 from croneter/fix-futurewarning
Fix FutureWarning
2019-06-28 16:46:51 +02:00
croneter
f5e8569584 Stable and beta version bump 2.8.6 2019-06-28 16:40:45 +02:00
croneter
eb3e655213 Cleanup due to wrong assumption that kodi playlist hash was unique 2019-06-28 15:54:55 +02:00
croneter
58ba03b94b Ensure faulty index on Plex DB is correctly recreated on PKC update 2019-06-28 15:46:40 +02:00
croneter
fb76f49fbd Fix PKC creating thousands of playlists if there are 2 identical Kodi playlists 2019-06-28 15:20:22 +02:00
croneter
f32a8c534f Fix FutureWarning 2019-06-25 18:12:46 +02:00
croneter
49a0528161
Merge pull request #906 from croneter/beta-version
Bump master
2019-06-22 18:45:45 +02:00
croneter
6e67429133
Merge pull request #904 from croneter/version-bump
Stable and beta version bump 2.8.5
2019-06-22 18:45:12 +02:00
croneter
1f4baae970
Merge pull request #905 from croneter/update-translations
Update translations
2019-06-22 18:44:59 +02:00
croneter
6ee1a4b695
Merge pull request #903 from croneter/fix-uniqueid
Fix Trakt add-on not recognizing id of tv shows
2019-06-22 18:44:48 +02:00
croneter
2643798393 Update translations 2019-06-22 18:43:36 +02:00
croneter
5c67f0c0b0 Stable and beta version bump 2.8.5 2019-06-22 18:37:15 +02:00
croneter
671bbbd9a9 Fix Trakt add-on not recognizing id of tv shows 2019-06-22 18:34:12 +02:00
croneter
8aa511a3d3
Merge pull request #901 from croneter/beta-version
Bump master
2019-06-16 14:17:41 +02:00
croneter
0c4a7da5e7
Merge pull request #900 from croneter/version-bump
Stable and beta version bump 2.8.4
2019-06-16 14:17:01 +02:00
croneter
f506b971ce
Merge pull request #899 from croneter/fix-listitem
Fix for Kodi 17 Krypton TypeError on playback start: 'offscreen' is an invalid keyword argument for this function
2019-06-16 14:16:35 +02:00
croneter
c91f62108b
Merge pull request #898 from croneter/fix-skin
Fix widgets not being populated after very first PlexKodiConnect library sync
2019-06-16 14:16:20 +02:00
croneter
0b9297e7c9
Merge pull request #895 from croneter/fix-install
Don't restart Kodi if user chose to enter PKC settings on install
2019-06-16 14:16:08 +02:00
croneter
9eb596954e Stable and beta version bump 2.8.4 2019-06-16 14:14:48 +02:00
croneter
68cf59293f Fix for Kodi 17 Krypton TypeError on playback start: 'offscreen' is an invalid keyword argument for this function 2019-06-16 14:11:53 +02:00
croneter
67dfca32b5 Fix widgets not being populated after first PKC sync 2019-06-16 13:49:42 +02:00
croneter
b99b17d0f9 Don't restart Kodi if user chose to enter PKC settings on install 2019-06-15 16:01:02 +02:00
croneter
495e0ef99b
Merge pull request #894 from croneter/beta-version
Bump master
2019-06-15 11:01:32 +02:00
croneter
966d368261
Merge pull request #893 from croneter/version-bump
Stable and beta version bump 2.8.3
2019-06-15 11:00:43 +02:00
croneter
0ae7c8ccfc Stable and beta version bump 2.8.3 2019-06-15 10:59:35 +02:00
croneter
b5b78669a2
Merge pull request #891 from croneter/version-bump
Beta version bump 2.8.2
2019-06-14 20:41:54 +02:00
croneter
39892a2aeb
Merge pull request #892 from croneter/update-translations
Update translations
2019-06-14 20:41:31 +02:00
croneter
097eb9d077 Update translations 2019-06-14 20:38:48 +02:00
croneter
3627c9e6bb
Merge pull request #890 from croneter/widget-limit
Improve some PKC widgets
2019-06-14 20:03:06 +02:00
croneter
d4f23db945
Merge pull request #889 from croneter/fix-tvshows
Fix In Progress widgets being broken and tv shows showing up as completely watched
2019-06-14 20:02:46 +02:00
croneter
ccc9372e9f
Merge pull request #888 from croneter/fix-typeerror
Fix TypeError for Direct Paths: init() got an unexpected keyword argument ‘item’
2019-06-14 20:02:31 +02:00
croneter
b913486da0 Beta version bump 2.8.2 2019-06-14 20:01:29 +02:00
croneter
a463bd521f Add an additional, faster On Deck node for movies 2019-06-14 18:07:19 +02:00
croneter
0bf7ada6e3 Delete Plex video node files when migrating to PKC version 2.8.2 2019-06-14 12:46:37 +02:00
croneter
1c245683ad Introduce limits to the number of videos shown in PKC widgets to speed them up 2019-06-14 12:38:01 +02:00
croneter
12b84b42bb Fix In Progress widgets not displaying anything and all shows being marked as watched 2019-06-14 12:14:39 +02:00
croneter
a761a8987d Fix TypeError for Direct Paths: init() got an unexpected keyword argument ‘item’ 2019-06-14 11:11:00 +02:00
croneter
fde67483f4
Merge pull request #882 from croneter/version-bump
Beta version bump 2.8.1
2019-06-12 20:13:37 +02:00
croneter
cd524cb978
Merge pull request #883 from croneter/fix-valueerror
Fix ValueError if casting to int/float
2019-06-12 20:05:37 +02:00
croneter
7018aba655
Merge pull request #884 from croneter/fix-runtimeerror
Fix playback startup and RuntimeError: Unknown exception thrown from the call "XBMCAddon::xbmcplugin::setResolvedUrl"
2019-06-12 20:05:16 +02:00
croneter
5ccf5d0b3e Beta version bump 2.8.1 2019-06-12 20:04:25 +02:00
croneter
e433f6f719 Fix RuntimeError: Unknown exception thrown from the call "XBMCAddon::xbmcplugin::setResolvedUrl" 2019-06-12 19:59:16 +02:00
croneter
e08e9b0d32 Fix ValueError if casting to int/float 2019-06-12 13:17:17 +02:00
croneter
563e86c8db
Merge pull request #880 from croneter/playlist-sync-dialog
Add additional info dialog for PKC synching playlists
2019-06-12 13:04:04 +02:00
croneter
04a6db6a21
Merge pull request #866 from croneter/fix-unicode
Fix rare UnicodeDecodeError on library sync
2019-06-12 13:03:48 +02:00
croneter
03a9f84b9e Fix rare UnicodeDecodeError on library sync 2019-06-12 13:02:29 +02:00
croneter
15f34e2859
Merge pull request #881 from croneter/update-translations
Update translations
2019-06-12 12:57:57 +02:00
croneter
c5a720aea9
Merge pull request #871 from croneter/refactor
Refactor Plex API
2019-06-12 12:57:34 +02:00
croneter
15f6a2a919 Update translations 2019-06-12 12:34:02 +02:00
croneter
1cbd65861c Add additional info dialog for PKC synching playlists 2019-06-12 12:18:09 +02:00
croneter
d96ac192d1 Fix casting to int silently failing 2019-06-12 11:53:41 +02:00
croneter
a46cb731cf Refactor Plex API 2019-06-12 11:53:41 +02:00
croneter
98c4a18ebf
Merge pull request #878 from croneter/beta-version
Bump master
2019-06-11 17:51:12 +02:00
croneter
4c6e71779b
Merge pull request #877 from croneter/croneter-patch-1
Update FUNDING.yml
2019-06-11 17:50:21 +02:00
croneter
df53f52a5d Update FUNDING.yml 2019-06-11 17:48:41 +02:00
croneter
7d38b205b5
Merge pull request #876 from croneter/beta-version
Bump master
2019-06-11 17:47:18 +02:00
croneter
039dfebf82
Merge pull request #875 from croneter/croneter-patch-1
Update FUNDING.yml
2019-06-11 17:46:37 +02:00
croneter
df7c757573 Update FUNDING.yml 2019-06-11 17:45:41 +02:00
croneter
b6fb97cec9
Merge pull request #874 from croneter/beta-version
Bump master
2019-06-11 12:07:46 +02:00
croneter
89ec68e22f
Merge pull request #873 from croneter/croneter-patch-1
Create FUNDING.yml
2019-06-11 11:10:06 +02:00
croneter
1b18edd3da Create FUNDING.yml 2019-06-11 11:09:14 +02:00
croneter
00f69d8568
Merge pull request #868 from croneter/beta-version
Bump master
2019-06-04 20:18:24 +02:00
croneter
844de523d4
Merge pull request #867 from croneter/version-bump
Stable and beta version bump 2.8.0
2019-06-04 20:17:43 +02:00
croneter
f805f2b265 Stable and beta version bump 2.8.0 2019-06-04 20:16:45 +02:00
croneter
dce9206571
Merge pull request #865 from croneter/version-bump
Beta version bump 2.7.18
2019-06-02 20:04:45 +02:00
croneter
3076be10e6
Merge pull request #864 from croneter/fix-resume
Fixes to playback startup
2019-06-02 20:03:25 +02:00
croneter
dab699345b Beta version bump 2.7.18 2019-06-02 19:53:23 +02:00
croneter
8e8cf18c0a Add comment: potential fix Kodi to immediately resuming the video if the user restarted playback 2019-06-02 19:30:28 +02:00
croneter
6bd5b28e0c Fix playback when the same video is started again from a widget 2019-06-02 13:47:50 +02:00
croneter
3ba09e1ff0 Increase logging 2019-06-02 13:30:23 +02:00
croneter
d0c0ab2a56 Fix video thrown by Plex Companion not resuming 2019-06-02 13:12:04 +02:00
croneter
04d328de3e Improve video resuming behavior; add a resume dialog for playback startup via PMS 2019-06-02 13:12:03 +02:00
croneter
bcb89aed88 Also play trailers if user chose to resume movie from the beginning 2019-06-01 16:15:14 +02:00
croneter
ccb95a0169 Fix Kodi always playing the same file version of a video 2019-06-01 16:15:14 +02:00
croneter
65f04ddefc
Merge pull request #862 from croneter/version-bump
Beta version bump 2.7.17
2019-05-30 14:33:05 +02:00
croneter
ab47b63bc5
Merge pull request #861 from croneter/fix-crash
Another attempt to keep Kodi from crashing on playback startup
2019-05-30 14:32:37 +02:00
croneter
58189ecf60 Beta version bump 2.7.17 2019-05-30 14:31:20 +02:00
croneter
32399decb6 Increase helper dependencies 2019-05-30 14:24:55 +02:00
croneter
9171169956 Another attempt to keep Kodi from crashing on playback startup 2019-05-30 14:24:18 +02:00
croneter
54998edf9f
Merge pull request #860 from croneter/version-bump
Beta version bump 2.7.16
2019-05-29 21:10:55 +02:00
croneter
9684540189
Merge pull request #859 from croneter/fix-crash
Hopefully fix Kodi crashing on playback startup for good
2019-05-29 21:10:35 +02:00
croneter
ca4e238359 Beta version bump 2.7.16 2019-05-29 21:05:13 +02:00
croneter
10c7c79035 Hopefully fix Kodi crashing on playback startup for good 2019-05-29 21:01:51 +02:00
croneter
14bec79d03
Merge pull request #857 from croneter/version-bump
Beta version bump 2.7.15
2019-05-28 20:12:26 +02:00
croneter
3e2e538e70
Merge pull request #856 from croneter/fix-crash
Hopefully fix Kodi crashing on playback startup
2019-05-28 20:12:08 +02:00
croneter
3e7533d965
Merge pull request #845 from croneter/update-widgets
Refresh widgets only on homescreen to prevent cursor from jumping within libraries
2019-05-28 20:11:49 +02:00
croneter
59aa9f3a55
Merge pull request #844 from croneter/no-refresh
Don't refresh container when user chose to delete or refresh an item from the context menu
2019-05-28 20:11:24 +02:00
croneter
a22ac545ac Beta version bump 2.7.15 2019-05-28 19:13:34 +02:00
croneter
1453b6c2f1
Merge pull request #855 from croneter/revert_libraryUUID
Revert "Merge pull request #841 from croneter/section-uuid"
2019-05-28 18:57:13 +02:00
croneter
23547a354c Hopefully fix Kodi crashing on playback startup 2019-05-28 18:55:29 +02:00
croneter
f993373a4f Revert "Merge pull request #841 from croneter/section-uuid"
This reverts commit edb9d6e2b0, reversing
changes made to a603bd33ae.
2019-05-28 18:42:04 +02:00
croneter
7e829c1bad Refresh widgets only on homescreen to prevent cursor from jumping within libraries 2019-05-01 08:56:11 +02:00
croneter
1a7a36820a Don't refresh container when use chose to delete or refresh an item 2019-05-01 08:35:32 +02:00
croneter
edb9d6e2b0
Merge pull request #841 from croneter/section-uuid
Safe Plex librarySectionUUID to our plex.db to speed up certain operations
2019-04-27 12:07:27 +02:00
croneter
ab8089d3b1 Rename section.section_id to section.id 2019-04-27 11:52:42 +02:00
croneter
e76fd03915 Safe section uuid when synching 2019-04-27 11:52:42 +02:00
croneter
ab5ab966e4 Use section for sync process 2019-04-27 11:52:42 +02:00
croneter
6a292d29f6 Safe Plex library section uuid to plex.db instead of kodi tagid 2019-04-27 11:52:42 +02:00
croneter
71a150ea09
Merge pull request #839 from croneter/beta-version
Bump master
2019-04-21 10:07:23 +02:00
croneter
a603bd33ae
Merge pull request #838 from croneter/version-bump
Stable and beta version bump 2.7.14
2019-04-21 10:06:46 +02:00
croneter
cc854a120f
Merge pull request #837 from croneter/fix-lastplayed
Fix last-played node value to ensure a playcount greater than zero
2019-04-21 10:06:20 +02:00
croneter
5c11ab21ed
Merge pull request #834 from croneter/reload-skin
Reload skin on resetting PKC video nodes
2019-04-21 10:05:43 +02:00
croneter
8dd1565fc1
Merge pull request #833 from croneter/fix-clearing-window-vars
Correctly clear window variables e.g. on user switch
2019-04-21 10:05:26 +02:00
croneter
01cabf0dac Stable and beta version bump 2.7.14 2019-04-21 10:04:59 +02:00
croneter
9b45a84d7e Correctly clear window variables e.g. on user switch 2019-04-20 17:28:43 +02:00
croneter
453098ef4f Fix last played node value for playcount 2019-04-20 14:16:54 +02:00
croneter
0c60f984d1 Reload skin on resetting PKC video nodes 2019-04-17 16:19:57 +02:00
croneter
37f1bddf9d
Merge pull request #830 from croneter/version-bump
Beta version bump 2.7.13
2019-04-14 14:56:36 +02:00
croneter
e554195228
Merge pull request #831 from croneter/fix-customization
Fix some appearance tweak settings
2019-04-14 14:56:14 +02:00
croneter
86c6e44ad6
Merge pull request #829 from croneter/fix-transcoding
Fix transcoding not working
2019-04-14 14:55:46 +02:00
croneter
203edf90f0
Merge pull request #828 from croneter/fix-4k
Fix 4k H265 not being transcoded
2019-04-14 14:55:35 +02:00
croneter
2791dc7b57
Merge pull request #827 from croneter/fix-nodes
Fix music and picture nodes pointing to video library
2019-04-14 14:55:19 +02:00
croneter
4dcf057631
Merge pull request #826 from croneter/fix-ne
Fix unequality when comparing sections
2019-04-14 14:55:06 +02:00
croneter
6f0cba6732
Merge pull request #821 from croneter/fix-plexhelper-log
Fix Plex Companion logging error messages
2019-04-14 14:54:51 +02:00
croneter
cdabb973f8 Beta version bump 2.7.13 2019-04-14 14:54:01 +02:00
croneter
b6f2a23622 Shorten string 2019-04-14 14:50:43 +02:00
croneter
15c4da46a9 Reenable setting: recently added: don't show watched movies 2019-04-14 14:49:44 +02:00
croneter
b22ae7aee4 Don't show old setting "Number of PMS items to show in widgets" 2019-04-14 14:45:42 +02:00
croneter
5cc67f41e1 Reenable setting: recently added: don't show watched episodes 2019-04-14 14:41:14 +02:00
croneter
5263f49c46 Remove obsolete setting: Extend Plex TV Series "On Deck" 2019-04-14 14:23:51 +02:00
croneter
eb1214f0c7 New setting to reloade Plex video nodes 2019-04-14 14:20:03 +02:00
croneter
75a6ecd1cf Fix transcoding not working 2019-04-14 13:13:48 +02:00
croneter
9c4c6bf07f Fix 4k H265 not being transcoded 2019-04-14 11:37:03 +02:00
croneter
34f1c7d1b9 Fix music and picture nodes pointing to video library 2019-04-14 11:03:05 +02:00
croneter
65e377a49f Remove obsolete import 2019-04-14 10:29:08 +02:00
croneter
02a03bd0f2 Fix unequality when comparing sections 2019-04-13 12:51:14 +02:00
croneter
e93fd94a71 Fix Plex Companion logging error messages 2019-04-05 17:54:03 +02:00
croneter
25c2b5e782
Merge pull request #820 from croneter/version-bump
Beta version bump 2.7.12
2019-04-05 17:13:23 +02:00
croneter
b178526ca1
Merge pull request #819 from croneter/fix-kodi-crash
Attempt to fix rare Kodi crash on PKC exit
2019-04-05 17:13:09 +02:00
croneter
fb879f39fb
Merge pull request #818 from croneter/fix-playback-startup
Fix UnicodeEncodeError on playback startup for direct paths
2019-04-05 17:12:58 +02:00
croneter
b1aceb1e90 Fix possible UnicodeEncodeError 2019-04-05 17:12:05 +02:00
croneter
11f636c4b9 Fix UnicodeEncodeError on playback startup 2019-04-05 17:12:05 +02:00
croneter
229c76eb39 Beta version bump 2.7.12 2019-04-05 16:51:16 +02:00
croneter
26689e16d4 Attempt to fix rare Kodi crash on PKC exit 2019-04-05 16:47:59 +02:00
croneter
d92ce36890
Merge pull request #811 from croneter/version-bump
Beta version bump 2.7.11
2019-03-30 17:50:54 +01:00
croneter
bca657ab08
Merge pull request #807 from croneter/fix-unicode
Fixes to unicode
2019-03-30 17:50:30 +01:00
croneter
d5a7bab8db
Merge pull request #809 from croneter/cleanup
Cleanup code, remove some obsolet methods and functions
2019-03-30 17:50:11 +01:00
croneter
fdc82d1d5b
Merge pull request #810 from croneter/fix-futurewarning
Fix FutureWarning
2019-03-30 17:49:57 +01:00
croneter
4262e52648 Beta version bump 2.7.11 2019-03-30 17:49:13 +01:00
croneter
aadb22e531 Fix FutureWarning 2019-03-30 17:34:51 +01:00
croneter
c1bb083933 Cleanup plex_api.py 2019-03-30 17:32:42 +01:00
croneter
1ac19109ba Turn urllib and urlparse unicode-safe 2019-03-30 17:32:42 +01:00
croneter
b4d036ed6b Monkey patch xml.etree.ElementTree to always return unicode 2019-03-30 09:56:22 +01:00
croneter
d5781d0906 Remove obsolete method 2019-03-30 09:54:31 +01:00
croneter
88cc4e220c Remove obsolete method 2019-03-30 09:54:24 +01:00
croneter
029123334b
Merge pull request #808 from croneter/codacy
Create .codacy.yaml to ignore certain Codacy errors
2019-03-30 09:52:50 +01:00
croneter
2929162671 Create .codacy.yaml 2019-03-30 09:51:56 +01:00
croneter
3208655598
Merge pull request #806 from croneter/beta-version
Bump master
2019-03-29 14:30:20 +01:00
croneter
d1bd785e5a
Merge pull request #804 from croneter/adjust-version-bump
Update changelog
2019-03-29 14:26:01 +01:00
croneter
5aa7e1d39b
Merge pull request #805 from croneter/update-translations
Update translations
2019-03-29 14:25:49 +01:00
croneter
068379c0eb
Merge pull request #802 from croneter/quick-fix-direct-paths
Fix UnicodeEncodeError for Direct Paths and some PKC video nodes
2019-03-29 14:25:37 +01:00
croneter
c849caa5de
Merge pull request #803 from croneter/revert-db-reset
Revert need to reset the Kodi DB
2019-03-29 14:25:26 +01:00
croneter
2c4fcfa581 Update translations 2019-03-29 14:24:28 +01:00
croneter
1a9af2f8a4 Update changelog 2019-03-29 14:06:54 +01:00
croneter
e1bdce2b99 Revert need to reset the Kodi DB 2019-03-29 14:03:46 +01:00
croneter
90f445cc75 Fix UnicodeEncodeError 2019-03-28 20:08:45 +01:00
croneter
2013b498d6 Add function to urlencode unicode 2019-03-28 20:07:40 +01:00
croneter
2bbc41d24d
Merge pull request #801 from croneter/version-bump
Stable and beta version bump 2.7.10
2019-03-28 14:05:37 +01:00
croneter
456897a3cc
Merge pull request #800 from croneter/fix-direct-music
Fix duplicate music entries for direct paths (forces a Kodi database reset!)
2019-03-28 14:04:53 +01:00
croneter
38cedfa706
Merge pull request #799 from croneter/fix-reporting
Fix playback sometimes not being reported for direct paths
2019-03-28 14:04:40 +01:00
croneter
e438c00b9b Stable and beta version bump 2.7.10 2019-03-28 14:02:45 +01:00
croneter
de80885aa5 Fix playback sometimes not being reported for direct paths 2019-03-28 13:48:50 +01:00
croneter
c308c06929 Fix duplicate music entries for direct paths (requires DB reset!) 2019-03-28 13:47:35 +01:00
croneter
0373029238
Merge pull request #796 from croneter/beta-version
Bump master
2019-03-26 18:53:27 +01:00
croneter
aedb16dc71
Merge pull request #795 from croneter/version-bump
Stable and beta version bump 2.7.9
2019-03-26 18:50:03 +01:00
croneter
de19813a85
Merge pull request #794 from croneter/fix-nfs-masterlock
Fix Kodi Masterlock for nfs paths (requires restart)
2019-03-26 18:49:50 +01:00
croneter
648d88b9b2
Merge pull request #791 from croneter/fix-widget-loading
Wait for PKC to authorize before loading widgets
2019-03-26 18:48:51 +01:00
croneter
c072f0f439
Merge pull request #790 from croneter/fix-startup
Fix TypeError on Kodi start
2019-03-26 18:48:34 +01:00
croneter
83660310bb
Merge pull request #789 from croneter/fix-unicode
Fix UnicodeDecodeError for libraries with non-ASCII paths
2019-03-26 18:48:14 +01:00
croneter
de888be777 Stable and beta version bump 2.7.9 2019-03-26 18:44:55 +01:00
croneter
5a8eb0db4b Fix Kodi Masterlock for nfs paths (requires restart) 2019-03-26 18:00:35 +01:00
croneter
8a2ce01aee Wait for PKC to authorize before loading widgets 2019-03-25 20:54:55 +01:00
croneter
448714e6a2 Fix TypeError on Kodi start 2019-03-25 20:51:28 +01:00
croneter
d05c29776c Fix UnicodeDecodeError for libraries with non-ASCII paths 2019-03-25 17:47:42 +01:00
croneter
ae328927f5
Merge pull request #785 from croneter/beta-version
Bump master
2019-03-21 20:36:23 +01:00
croneter
8d8b631889
Merge pull request #784 from croneter/version-bump
Stable and beta version bump 2.7.8
2019-03-21 20:35:41 +01:00
croneter
2c101d9145
Merge pull request #783 from croneter/fix-widgets
Fix node paths for skins
2019-03-21 20:35:14 +01:00
croneter
afc15af9cf
Merge pull request #782 from croneter/fix-nodes
Fix widgets not working in some cases, e.g. Nvidia Shield
2019-03-21 20:34:44 +01:00
croneter
f987b49048 Stable and beta version bump 2.7.8 2019-03-21 20:27:55 +01:00
croneter
c996d51085 Fix video node path for widgets 2019-03-21 20:22:41 +01:00
croneter
7cb6c75f3b Fix adding of show title, season and episode number 2019-03-21 20:06:52 +01:00
croneter
9665f34ee6 Fix Typo 2019-03-20 19:19:57 +01:00
croneter
34bc708d7b
Merge pull request #778 from croneter/beta-version
Bump master
2019-03-17 18:01:07 +01:00
croneter
3b0a746af6
Merge pull request #776 from croneter/version-bump
Stable and beta version bump 2.7.7
2019-03-17 18:00:34 +01:00
croneter
a8a229967c
Merge pull request #777 from croneter/fix-profile-switch
Fix PKC synching playstate to wrong user on profile switch. Be aware that Kodi profile switches are error-prone
2019-03-17 17:59:20 +01:00
croneter
58b15e69a9
Merge pull request #775 from croneter/fix-nodes
Fix nodes for skin use
2019-03-17 17:58:28 +01:00
croneter
392f3f6359
Merge pull request #774 from croneter/fix-unicode
Fix sync not working due to non-ASCII Plex library names
2019-03-17 17:58:15 +01:00
croneter
71af3d0ca6
Merge pull request #773 from croneter/fix-direct-paths
Fix playback sometimes not being reported for direct paths
2019-03-17 17:58:00 +01:00
croneter
b5b9dd51a5
Merge pull request #771 from croneter/fix-playlist-sync
Fix 'NoneType' object has no attribute 'kodi_path'
2019-03-17 17:57:49 +01:00
croneter
0ee65b9f6c
Merge pull request #770 from croneter/fix-timesync
Fix float() argument must be a string or a number
2019-03-17 17:57:35 +01:00
croneter
815cd20d39 Ensure Plex companion thread quits on time 2019-03-17 17:56:31 +01:00
croneter
9d07a58c50 Replace waitForAbort() with sleep() 2019-03-17 17:56:31 +01:00
croneter
39157385f2 Stable and beta version bump 2.7.7 2019-03-17 17:15:13 +01:00
croneter
d068c37c49 Fix PKC synching playstate to wrong user on profile switch
- BUT: Kodi profiles seem to be a mess!
2019-03-17 16:53:13 +01:00
croneter
ba4f1d15d6 Increase logging 2019-03-17 16:26:48 +01:00
croneter
3c70a84704 Catch all exceptions, just in case 2019-03-17 15:42:29 +01:00
croneter
37bbf61a63 Ensure that reference to threader is gone on shutdown 2019-03-17 15:33:42 +01:00
croneter
41483e6731 Add more exit points when synching 2019-03-17 15:31:02 +01:00
croneter
9a6485737f Fix 'all' video node not leading to all library items 2019-03-17 12:57:38 +01:00
croneter
a2b57909cb Hopefully fix On Deck video node not working 2019-03-17 12:57:38 +01:00
croneter
3d4642dc56 Fix sync not working due to non-ASCII Plex library names 2019-03-17 11:54:09 +01:00
croneter
68ca41545c Fix playback sometimes not being reported for direct paths 2019-03-17 11:41:43 +01:00
croneter
db0f7f283e Simplify code to get path and filename 2019-03-17 11:30:44 +01:00
croneter
eef0eda426 Fix 'NoneType' object has no attribute 'kodi_path' 2019-03-16 17:47:23 +01:00
croneter
c6e635b39b Fix float() argument must be a string or a number 2019-03-16 17:34:47 +01:00
croneter
0900e462c0
Merge pull request #769 from croneter/beta-version
Bump master
2019-03-16 17:17:19 +01:00
croneter
98975ff23d
Merge pull request #768 from croneter/version-bump
Stable and beta version bump 2.7.6
2019-03-16 17:16:31 +01:00
croneter
dfe281268f Stable and beta version bump 2.7.6 2019-03-16 17:15:23 +01:00
croneter
7d040e37ae
Merge pull request #759 from croneter/version-bump
Beta version bump 2.7.5
2019-03-10 18:06:12 +01:00
croneter
bda58deb6e
Merge pull request #722 from croneter/widget_overhaul
Giant overhaul of widgets
2019-03-10 18:01:13 +01:00
croneter
86cde7c69a
Merge pull request #760 from croneter/fix-keyerror
Fix some KeyErrors when playing songs
2019-03-10 18:00:53 +01:00
croneter
a2cd5bec76
Merge pull request #761 from croneter/fix-playlists
Fix rare cases where playlists were being created
2019-03-10 18:00:39 +01:00
croneter
e50c0c011e Allow searching the PMS 2019-03-10 18:00:05 +01:00
croneter
5b7595c681 Rework widgets and listitems 2019-03-10 18:00:04 +01:00
croneter
4ea77370e8 Beta version bump 2.7.5
- Includes a migration!
2019-03-10 13:27:27 +01:00
croneter
c99cead6f5 Ignore all file events for playlists caused by PKC 2019-03-10 13:22:38 +01:00
croneter
8c51ee5c7a Ignore all websocket playlist messages caused by PKC 2019-03-10 13:22:38 +01:00
croneter
62ecefdcca Less logging 2019-03-10 11:25:02 +01:00
croneter
c69176d4a1 Fix some KeyErrors when playing songs 2019-03-09 16:19:29 +01:00
croneter
416cf20686
Merge pull request #752 from croneter/beta-version
Bump master
2019-02-26 18:19:16 +01:00
croneter
9ba49a663e
Merge pull request #751 from croneter/version-bump
Stable and beta version bump 2.7.4
2019-02-26 18:18:27 +01:00
croneter
526f7d9fc7
Merge pull request #750 from croneter/fix-sync
Fix PKC not synching new items if an older Kodi db is present
2019-02-26 18:18:14 +01:00
croneter
07eeffbabc Stable and beta version bump 2.7.4 2019-02-26 18:16:34 +01:00
croneter
349829a754 Fix PKC not synching new items if an older Kodi db is present 2019-02-26 17:41:35 +01:00
croneter
652b23bd8d
Merge pull request #747 from croneter/beta-version
Bump master
2019-02-24 11:27:52 +01:00
croneter
3b9518d747
Merge pull request #746 from croneter/version-bump
Stable and beta version bump 2.7.3
2019-02-24 11:27:19 +01:00
croneter
c17dda40dd
Merge pull request #744 from croneter/fix-theme-log-entries
Fix PKC trying to initialize playqueues over and over again
2019-02-24 11:27:03 +01:00
croneter
c695df2520
Merge pull request #743 from croneter/fix-dsplayer
Fix PKC not starting due to a higher version Kodi database
2019-02-24 11:26:47 +01:00
croneter
05998c3b82 Stable and beta version bump 2.7.3 2019-02-24 11:25:37 +01:00
croneter
33cae69cc4 Fix PKC not starting due to a higher version Kodi database 2019-02-24 11:19:35 +01:00
croneter
36c78b0d6e Fix PKC trying to initialize playqueues over and over again 2019-02-23 08:53:37 +01:00
croneter
6af36be046
Merge pull request #741 from croneter/beta-version
Bump master
2019-02-22 15:47:18 +01:00
croneter
dffbaac7e0
Merge pull request #740 from croneter/version-bump
Stable and beta version bump 2.7.2
2019-02-22 15:46:09 +01:00
croneter
5e77dfb71e
Merge pull request #739 from croneter/fix-user
Fix Kodi profile switch not working correctly and PKC not exiting cleanly
2019-02-22 15:45:46 +01:00
croneter
a9ff70fac7 Fix PKC not exiting correctly due to a call to xbmc.getCondVisibility 2019-02-22 08:11:58 +01:00
croneter
2536cb8a79 Stable and beta version bump 2.7.2 2019-02-22 08:04:09 +01:00
croneter
fa9ca95e2e Remove obsolete check for Kodi profile switch 2019-02-21 09:15:29 +01:00
croneter
2c12f2e705 Update to new style Python class 2019-02-21 08:47:52 +01:00
croneter
3c9eb71324 Fix websocket threads not exiting correctly 2019-02-21 08:47:36 +01:00
croneter
1ce5ccda62
Merge pull request #736 from croneter/beta-version
Bump master
2019-02-16 19:09:24 +01:00
croneter
2ac9358cd4
Merge pull request #734 from croneter/version-bump
Stable and beta version bump 2.7.1
2019-02-16 19:06:35 +01:00
croneter
f63ef92b8f
Merge pull request #735 from croneter/update-translations
Update translations
2019-02-16 19:06:22 +01:00
croneter
c88dbc8687
Merge pull request #733 from croneter/fix-encoding
Fix playback not starting at all
2019-02-16 19:06:06 +01:00
croneter
78def504bc
Merge pull request #732 from croneter/fix-typeerror
Fix rare TypeError: unsupported operand type(s) for /: 'NoneType' and 'int' on playback startup
2019-02-16 19:05:50 +01:00
croneter
2942300f10
Merge pull request #731 from croneter/improve-db
Improve plex db lookups by creating better db indicees
2019-02-16 19:05:36 +01:00
croneter
596dbcc7dc
Merge pull request #729 from croneter/fix-keyerror
Fix background sync crashing in rare cases
2019-02-16 19:05:26 +01:00
croneter
ba967b212b
Merge pull request #726 from croneter/readme
Add Ko-fi donate button
2019-02-16 19:05:11 +01:00
croneter
a45cb02bc0 Update translations 2019-02-16 19:04:01 +01:00
croneter
bf9ef7afbe Stable and beta version bump 2.7.1 2019-02-16 18:43:27 +01:00
croneter
098ad30432 Bump dependency add-ons 2019-02-16 18:28:48 +01:00
croneter
4a83aae573 Fix TypeError: unsupported operand type(s) for /: 'NoneType' and 'int' 2019-02-16 17:59:03 +01:00
croneter
056150a2da Improve plex db lookups by creating better db indicees 2019-02-16 14:05:09 +01:00
croneter
823f6b6e3f Fix KeyError: u'kodi_type' 2019-02-16 12:17:56 +01:00
croneter
f6e3d25413 Add Ko-fi donate button 2019-02-14 12:37:32 +01:00
croneter
0e64f50b95
Merge pull request #721 from croneter/beta-version
Update master
2019-02-11 16:59:02 +01:00
croneter
0d94a633e0
Merge pull request #720 from croneter/version-bump
Stable and beta version bump 2.7.0
2019-02-11 16:57:27 +01:00
croneter
d483a5b6ee Stable and beta version bump 2.7.0 2019-02-11 16:43:01 +01:00
croneter
deffd73fa3
Merge pull request #719 from croneter/version-bump
Beta version bump 2.6.9
2019-02-08 15:36:58 +01:00
croneter
b31a2c6b35
Merge pull request #718 from croneter/fix-unresponsive
Fix PKC crashing on resetting the database
2019-02-08 15:36:48 +01:00
croneter
7fa86ac097 Beta version bump 2.6.9 2019-02-08 15:34:57 +01:00
croneter
94da14744d Fix PKC crashing on recreating the database 2019-02-08 15:33:29 +01:00
croneter
d8aada43a6
Merge pull request #714 from croneter/version-bump
Beta version bump 2.6.8
2019-02-08 15:20:25 +01:00
croneter
a9bfae2b13
Merge pull request #715 from croneter/codacy
Implement Codacy suggestions
2019-02-08 15:20:11 +01:00
croneter
7a0b9c8fca
Merge pull request #713 from croneter/fix-collections
Fix processing of collections in special cases
2019-02-08 15:20:00 +01:00
croneter
80f3542737
Merge pull request #712 from croneter/fix-videos-disappearing
Fix movies or shows disappearing in fringe cases
2019-02-08 15:19:50 +01:00
croneter
da1cb6e3d7
Merge pull request #717 from croneter/fix-playlists
Fix rare case where thousands of identical playlists could be generated
2019-02-08 15:19:37 +01:00
croneter
a0d6674f7e
Merge pull request #711 from croneter/fix-unresponsive
Fix PKC becoming unresponsive
2019-02-08 15:19:23 +01:00
croneter
aa27b4ad1f
Merge pull request #710 from croneter/select-library
Choose which Plex libraries get synched to Kodi
2019-02-08 15:19:12 +01:00
croneter
d617535a05 Beta version bump 2.6.8 2019-02-08 15:18:41 +01:00
croneter
873c9e74f5 Fix rare case where thousands of identical playlists could be generated 2019-02-08 15:15:52 +01:00
croneter
e6cd80a7dd Implement Codacy suggestions 2019-02-08 13:52:33 +01:00
croneter
1a1e4b113d Prompt user to select libraries to sync upon changing PMS 2019-02-08 13:24:14 +01:00
croneter
50d770718d Choose which Plex libraries PKC should sync 2019-02-08 13:24:14 +01:00
croneter
6447281404 Fix processing of collections in special cases 2019-02-08 13:02:55 +01:00
croneter
f98e2df42f Fix movies or shows disappearing in fringe cases 2019-02-08 13:01:10 +01:00
croneter
cd728b4c32 Fix PKC becoming unresponsive 2019-02-08 12:59:12 +01:00
croneter
3b18a92ab4 Fix PKC becoming unresponsive 2019-02-08 12:58:53 +01:00
croneter
f24266fb54
Merge pull request #709 from croneter/version-bump
Beta version bump 2.6.7
2019-02-06 16:19:14 +01:00
croneter
bdb85293a5 Beta version bump 2.6.7 2019-02-06 16:18:22 +01:00
croneter
05a6700d55
Merge pull request #708 from croneter/improve-playerror-messages
Improve error messages when playback failes
2019-02-06 16:16:09 +01:00
croneter
8621c57de9
Merge pull request #655 from croneter/unauth_on_user_switch
Fix "Unauthorized for PMS" e.g. on switching Plex users
2019-02-06 16:15:54 +01:00
croneter
c7eab63960 Improve error message when playback failes 2019-02-06 16:14:14 +01:00
croneter
1787e51c7c Zentrally register threads and introduce a way to wait for their suspension 2019-02-06 14:19:10 +01:00
croneter
63201db07d
Merge pull request #707 from croneter/version-bump
Beta version bump 2.6.6
2019-02-05 18:59:53 +01:00
croneter
d8796a174e
Merge pull request #706 from croneter/fix-pms-logs
Optimize headers for communication with PMS to appear like a Plex Media Player
2019-02-05 18:59:43 +01:00
croneter
6bf0cf4894
Merge pull request #705 from croneter/improve-sync-dialog
Improve sync dialog
2019-02-05 18:59:28 +01:00
croneter
950a2de0f5
Merge pull request #704 from croneter/fix-slow-sync
Greatly speed up sync for episodes, especially for large libraries
2019-02-05 18:59:16 +01:00
croneter
d8770603ba
Merge pull request #703 from croneter/fix-websocket
Allow websocket redirects. Never allow insecure HTTPs connections for Kodi Leia
2019-02-05 18:59:04 +01:00
croneter
74dc249a37 Enforce database reset 2019-02-05 18:57:50 +01:00
croneter
63241b127e Beta version bump 2.6.6 2019-02-05 18:57:22 +01:00
croneter
9208cf2cb3 Optimize headers for communication with PMS to appear like PMP 2019-02-05 18:52:10 +01:00
croneter
01c0c36244 Increase batch size for playstates by a factor 10 2019-02-05 16:54:40 +01:00
croneter
ca16f73637 Make sure we're not showing an item's title while synching playstate 2019-02-05 15:48:24 +01:00
croneter
4786372490 Improve sync dialog for Estuary by switching item's title and sync count 2019-02-05 15:48:02 +01:00
croneter
2234e49cf8 Greatly speed up episode sync for large libraries 2019-02-05 15:32:50 +01:00
croneter
e65c9d6c94 Increase sync processing batch size from 200 to 500 2019-02-05 13:21:08 +01:00
croneter
0e16eb703a Do not allow insecure HTTPS websocket connections for Kodi Leia in any case 2019-02-05 13:10:00 +01:00
croneter
7b21caceae Allow websocket redirects 2019-02-05 13:09:59 +01:00
croneter
a6dd429b79
Merge pull request #701 from croneter/beta-version
Bump master
2019-02-04 16:30:22 +01:00
croneter
548d83874b
Merge pull request #700 from croneter/version-bump
Stable and beta version bump 2.6.5
2019-02-04 16:27:04 +01:00
croneter
03e7fc6791 Stable and beta version bump 2.6.5 2019-02-04 16:26:08 +01:00
croneter
bf6e52bf20
Merge pull request #698 from croneter/improve-logging
Improve logging
2019-02-04 16:23:49 +01:00
croneter
664470df6a
Merge pull request #697 from croneter/fix-extras
Fix extras not playing
2019-02-04 16:23:36 +01:00
croneter
edb72fba36
Merge pull request #696 from croneter/hide-ssl-setting
Hide "Verify SSL certificate" setting for Kodi 18 Krypton
2019-02-04 16:23:23 +01:00
croneter
8d509369e8
Merge pull request #699 from croneter/update-translations
Update translations
2019-02-04 16:23:11 +01:00
croneter
426318a552 Update translations 2019-02-04 16:21:52 +01:00
croneter
76004f1aa6 Improve logging 2019-02-04 16:15:58 +01:00
croneter
20225f12b4 Fix extras not playing 2019-02-04 12:51:16 +01:00
croneter
8169907192 Hide "Verify SSL certificate" setting for Kodi 18 Krypton 2019-02-04 10:30:55 +01:00
croneter
85f73126ff
Merge pull request #695 from croneter/beta-version
Bump stable
2019-02-03 21:04:52 +01:00
croneter
29ec8cabf7
Merge pull request #694 from croneter/version-bump
Stable and beta version bump 2.6.4
2019-02-03 20:54:53 +01:00
croneter
2c15e0e1f7
Merge pull request #693 from croneter/update_german
Update German strings
2019-02-03 20:54:28 +01:00
croneter
995d5a5ba8 Stable and beta version bump 2.6.4 2019-02-03 20:53:32 +01:00
croneter
b206d1a99f Update German strings 2019-02-03 20:50:21 +01:00
croneter
2b0d63da6f
Merge pull request #691 from croneter/change-user
Show logged in Plex home user in the settings and allow changing it
2019-02-03 20:32:06 +01:00
croneter
033919d09b
Merge pull request #690 from croneter/fix-music
Fix music items getting deleted on startup
2019-02-03 20:31:53 +01:00
croneter
7acad7c268
Merge pull request #688 from croneter/enforce_ssl
Never ignore SSL certificate errors for Kodi >= 18 - just like Kodi
2019-02-03 20:31:40 +01:00
croneter
f90b470f5b Never ignore SSL certificate errors for Kodi >= 18 - just like Kodi 2019-02-03 20:28:50 +01:00
croneter
ae3f24f6ca
Merge pull request #687 from croneter/manual_ip
Improve dialog to manually enter PMS IP and port
2019-02-03 20:25:48 +01:00
croneter
a7222af648
Merge pull request #686 from croneter/fix-resume
Fix playback not starting at the beginning
2019-02-03 20:24:04 +01:00
croneter
4edea1c7a0
Merge pull request #684 from croneter/codacy
Implement Codacy suggestions
2019-02-03 20:23:49 +01:00
croneter
2c77bd28af Implement Codacy suggestions 2019-02-03 20:22:41 +01:00
croneter
662ed1bdfc Show logged in Plex home user in the settings and allow changing it 2019-02-03 19:12:35 +01:00
croneter
8f8ccd1daf Fix music items getting deleted on startup 2019-02-03 17:20:36 +01:00
croneter
01609e4d72 Better inform the user if problems arise with manual PMS IP input 2019-02-03 16:44:37 +01:00
croneter
1071d75857 Improve dialog to manually enter PMS IP and port 2019-02-03 10:53:19 +01:00
croneter
09c2ad1b80 Fix playback not starting at the beginning 2019-02-02 20:50:01 +01:00
croneter
412dc254fc
Merge pull request #683 from croneter/beta-version
Bump beta
2019-02-02 16:00:02 +01:00
croneter
1dbeb95e24
Merge pull request #682 from croneter/version-bump
Stable and beta version bump 2.6.3
2019-02-02 15:59:17 +01:00
croneter
e0d3670299
Merge pull request #681 from croneter/pr_ctypes
Fix PKC crashing on Xbox
2019-02-02 15:59:03 +01:00
croneter
6c8b17d7b8 Fix PKC crashing on Xbox 2019-02-02 15:58:07 +01:00
croneter
0e1ea79f3c Stable and beta version bump 2.6.3 2019-02-02 15:52:11 +01:00
croneter
fb5d84034c
Merge pull request #680 from croneter/beta-version
Beta version
2019-02-02 15:06:51 +01:00
croneter
0c567509eb
Merge pull request #679 from croneter/issue_template
Fix typo
2019-02-02 15:06:25 +01:00
croneter
5acfca44e5 Fix typo 2019-02-02 15:05:31 +01:00
croneter
a80be43e13
Merge pull request #678 from croneter/beta-version
Beta version
2019-02-02 15:04:15 +01:00
croneter
0d8692c336
Merge pull request #677 from croneter/issue_template
Add issue template for bug report
2019-02-02 15:03:36 +01:00
croneter
2126a8e2c2 Add issue template for bug report 2019-02-02 15:02:44 +01:00
croneter
ee68703da9
Merge pull request #675 from croneter/beta-version
Bump stable
2019-02-02 13:47:11 +01:00
croneter
a9883d8a3f
Merge pull request #674 from croneter/pr_version-bump
Stable and beta version bump 2.6.2
2019-02-02 13:43:46 +01:00
croneter
7f0abcee7d
Merge pull request #673 from croneter/pr_improve_dialogs
Improve pop-up "Searching for PMS"
2019-02-02 13:43:34 +01:00
croneter
cb97734cf9
Merge pull request #671 from croneter/pr_fix_futurewarning
Fix FutureWarning
2019-02-02 13:43:24 +01:00
croneter
0de47c7b3e
Merge pull request #670 from croneter/pr_attributeerror
Fix artwork caching AttributeError: 'ImageCachingThread' object has no attribute 'cancel'
2019-02-02 13:43:09 +01:00
croneter
10e6caf3cd
Merge pull request #669 from croneter/pr_fix_delete
Fix PKC not deleting all the items it should
2019-02-02 13:42:58 +01:00
croneter
1d17dd867c
Merge pull request #668 from croneter/pr_sequence_item
Fix playlist sync: sequence item 0: expected string or unicode
2019-02-02 13:39:47 +01:00
croneter
06fe382a3f
Merge pull request #667 from croneter/pr-keyError
Fix keyError 'sessionKey' for weird PMS messages
2019-02-02 13:39:31 +01:00
croneter
7d7d40bc0d Stable and beta version bump 2.6.2 2019-02-02 13:29:46 +01:00
croneter
b858373aeb Also show a pop-up if PKC searches for changed PMS address on PKC startup 2019-02-02 13:24:31 +01:00
croneter
65fe1ed399 Show info pop-up "Searching for PMS" until PKC displays the results 2019-02-02 13:02:45 +01:00
croneter
fb9c560ccd Fix FutureWarning 2019-02-02 11:34:16 +01:00
croneter
996adc2c03 Fix artwork caching AttributeError 2019-02-02 11:30:03 +01:00
croneter
3a9fcacd5c Fix PKC not deleting all the items it should 2019-02-02 11:24:55 +01:00
croneter
d09d2e6aaf Fix playlist sync: sequence item 0: expected string or unicode 2019-02-02 10:53:10 +01:00
croneter
fdbe42a05a Fix keyError sessionKey for weird PMS messages 2019-02-02 10:42:57 +01:00
croneter
ad607f61e7
Merge pull request #653 from croneter/beta-version
Bump stable version
2019-01-30 18:31:38 +01:00
croneter
b3402fc3d5
Merge pull request #652 from croneter/version-bump
Stable and beta version bump 2.6.1
2019-01-30 18:27:25 +01:00
croneter
dfeba07ab6
Merge pull request #648 from croneter/user_fix
Fix TV sections not being deleted e.g. after user switch
2019-01-30 18:27:12 +01:00
croneter
4acd826e39 Stable and beta version bump 2.6.1 2019-01-30 18:26:23 +01:00
croneter
5f72d50ed0
Merge pull request #647 from croneter/less_dialogs
Don't show a library sync error pop-up when full sync is interrupted
2019-01-30 18:23:46 +01:00
croneter
ae7eaadd1e
Merge pull request #645 from croneter/http_direct_paths
Fix to correctly escape paths
2019-01-30 18:23:30 +01:00
croneter
635f941a30
Merge pull request #651 from croneter/update_translations
Update translations
2019-01-30 18:23:15 +01:00
croneter
c388789fbd Add error notification is full sync is unsuccessful 2019-01-30 18:05:28 +01:00
croneter
a39127af1c Update translations 2019-01-30 17:57:21 +01:00
croneter
4e10a9da8b Force database reset 2019-01-30 17:40:20 +01:00
croneter
8cb4e5f8fc Fix to cleanly delete all songs and albums 2019-01-30 15:54:19 +01:00
croneter
624fe87fd1 Fix to cleanly delete all episodes and seasons 2019-01-30 15:54:19 +01:00
croneter
a279cf5198 Fix deleting entire Plex library sections
Typo plex_type - section_type
2019-01-30 15:54:13 +01:00
croneter
dcff85e203 Fix TV sections not being deleted e.g. after user switch 2019-01-30 10:58:20 +01:00
croneter
5298370413 Don't show an error pop-up if full sync is interrupted 2019-01-30 09:34:38 +01:00
croneter
5f1241adde Fix to correctly escape paths 2019-01-29 13:52:23 +01:00
croneter
1e499ebfe6
Merge pull request #643 from croneter/addon_xml
Update Italien addon.xml descriptions of PKC Kodi add-on
2019-01-29 08:35:44 +01:00
croneter
5acd936b05 Update Italien addon.xml descriptions of PKC Kodi add-on 2019-01-29 08:34:56 +01:00
croneter
29f3aa84aa
Merge pull request #642 from croneter/version-bump
Stable and beta version bump 2.6.0
2019-01-29 08:27:23 +01:00
croneter
d10011cc50
Merge pull request #641 from croneter/update_translations
Update translations
2019-01-29 08:27:14 +01:00
croneter
b5d0e4f428
Merge pull request #640 from croneter/fix_kodi_fileid
Fix KeyError: u'kodi_fileid' for some Plex websocket messages
2019-01-29 08:27:05 +01:00
croneter
e566dc8515
Merge pull request #639 from croneter/websocket_lock_db
Don't lock Plex DB when processing websocket messages
2019-01-29 08:26:55 +01:00
croneter
2511567e96 Stable and beta version bump 2.6.0 2019-01-29 08:25:23 +01:00
croneter
a09dfd2b0d Update translations 2019-01-29 08:16:56 +01:00
croneter
3450bc5294 Don't lock Plex DB when processing websocket messages 2019-01-29 08:09:42 +01:00
croneter
91fe4f827d Fix KeyError: u'kodi_fileid' for some Plex websocket messages 2019-01-29 08:05:15 +01:00
croneter
0c400b3563
Merge pull request #637 from croneter/version-bump
Beta version bump 2.5.23
2019-01-28 20:22:28 +01:00
croneter
8f2a0c3742
Merge pull request #636 from croneter/update_translations
Update translations
2019-01-28 20:22:17 +01:00
croneter
0278e16de0
Merge pull request #635 from croneter/fix_slow_playback_startup
Hopefully fix slow playback startup just after Kodi startup
2019-01-28 20:22:05 +01:00
croneter
9ec9278888
Merge pull request #633 from croneter/revamp_direct_paths
Better, safer way to enter network credentials for Direct Paths
2019-01-28 20:21:48 +01:00
croneter
77ca3c428e
Merge pull request #632 from croneter/path_exists
Fix check whether a direct path is accessible
2019-01-28 20:21:38 +01:00
croneter
ee805301a3
Merge pull request #630 from croneter/operationalerror
Fix OperationalError: no such table on database reset
2019-01-28 20:21:25 +01:00
croneter
06964fab7d
Merge pull request #627 from croneter/missing_items
Fix 'NoneType' object has no attribute 'execute' when Plex artwork is not synced and an item is deleted
2019-01-28 20:21:14 +01:00
croneter
96db37c588
Merge pull request #626 from croneter/optimize_logging
Log whether Plex artwork is synced to Kodi
2019-01-28 20:21:03 +01:00
croneter
62e914d7e5
Merge pull request #624 from croneter/stuff
Fix widgets not displaying correct playstate after PKC startup
2019-01-28 20:20:50 +01:00
croneter
75c5bcae15 Beta version bump 2.5.23 2019-01-28 20:19:55 +01:00
croneter
1d95ef69b5 Update translations 2019-01-28 20:12:43 +01:00
croneter
652a43e05c Reduce batch size for processing playstate to 10% 2019-01-28 18:11:44 +01:00
croneter
e04c74392f Don't lock DBs when messing with the playqueue 2019-01-28 18:05:40 +01:00
croneter
a79ff338cc Clarify setting for Addon Paths and Direct Paths 2019-01-28 14:43:59 +01:00
croneter
2ad95f4cd1 Clarify and move setting for network credentials 2019-01-28 14:43:59 +01:00
croneter
907b22a60f Remove obsolete setting 2019-01-28 14:43:59 +01:00
croneter
cffebf2f65 Better, safer dialogs to enter network credentials 2019-01-28 14:43:58 +01:00
croneter
097cc79c5a Make Plex dialog larger 2019-01-28 14:43:58 +01:00
croneter
4be8b3da9b Add comment about need to have sources.xml and passwords.xml set-up 2019-01-28 09:23:10 +01:00
croneter
25aacc8495 Fix check whether a file exists 2019-01-28 09:22:36 +01:00
croneter
a3d2c5530c Fix OperationalError: no such table on database reset 2019-01-27 13:31:59 +01:00
croneter
aa2b1e5e15 Always open a connection to the Kodi texture DB 2019-01-27 09:27:12 +01:00
croneter
4779f3e994 Log whether Plex artwork is synced to Kodi 2019-01-27 08:33:15 +01:00
croneter
352402bc35 Revert "Refresh Kodi view only once on full syncs"
This reverts commit b86a6549ac.
2019-01-26 17:18:48 +01:00
croneter
1d991fd606
Merge pull request #621 from croneter/temp
Fix strings containing formatting information
2019-01-26 11:52:39 +01:00
croneter
cb563ba020 Fix strings containing formatting information 2019-01-26 11:51:27 +01:00
croneter
9e2805b622
Merge pull request #619 from croneter/pickle
Beta version bump 2.5.22
2019-01-26 09:26:08 +01:00
croneter
c351b93635 Beta version bump 2.5.22 2019-01-26 09:24:22 +01:00
croneter
15c0322f27
Merge pull request #618 from croneter/pickle
Fix rare EOFError and PKC starting wrong video as a consequence
2019-01-26 09:22:40 +01:00
croneter
83ee4c29ba Bump dependency on plugin.video.plexkodiconnect.movies 2019-01-26 09:00:34 +01:00
croneter
6d0ad14339 Bump dependency on plugin.video.plexkodiconnect.tvshows 2019-01-26 08:45:03 +01:00
croneter
69cb09e009 Replace cPickle communication with JSON 2019-01-26 08:43:51 +01:00
croneter
67db141d73 Revert "Increase dependency for plugin.video.plexkodiconnect.movies and tvshows"
This reverts commit 087f0bcd23.
2019-01-25 17:30:07 +01:00
croneter
211d3d6a5a Revert "Use highes available protocol for cPickle"
This reverts commit 32d8083b60.
2019-01-25 17:30:03 +01:00
croneter
2ee3d86c27 Revert "Don't use class attributes for cPickle"
This reverts commit e181b4cac2.
2019-01-25 17:29:59 +01:00
croneter
087f0bcd23 Increase dependency for plugin.video.plexkodiconnect.movies and tvshows 2019-01-25 15:44:21 +01:00
croneter
32d8083b60 Use highes available protocol for cPickle 2019-01-25 15:43:45 +01:00
croneter
e181b4cac2 Don't use class attributes for cPickle
See https://docs.python.org/2/library/pickle.html#what-can-be-pickled-and-unpickled
2019-01-25 15:43:17 +01:00
croneter
4739337ed5 Beta version bump 2.5.21 2019-01-23 10:02:50 +01:00
croneter
5de91ff9b5 Fix KodiVideoDB object has no attribute kodiconn
- Partially fixes #593
- Fixes #614
2019-01-23 10:00:49 +01:00
croneter
d6246a1cab Fix local variable 'set_api' referenced before assignment 2019-01-22 08:52:04 +01:00
croneter
1ed0deebc3 Beta version bump 2.5.20 2019-01-19 17:57:20 +01:00
croneter
9e6b841296 Begin a new transaction when database was locked
- Hopefully fixes #593
2019-01-19 17:53:35 +01:00
croneter
7527f38c3f Revert "Wait even longer to try to write to Kodi DB"
This reverts commit 9f0d0026e6.
2019-01-19 17:12:45 +01:00
croneter
f821134ed3 Add link for more sqlite info 2019-01-19 17:06:52 +01:00
croneter
5b5af1ceed Fix browsing to show from info dialog
- Fixes #603
- Increases dependency on plugin.video.plexkodiconnect.tvshows to 2.0.8
2019-01-19 16:56:10 +01:00
croneter
9f0d0026e6 Wait even longer to try to write to Kodi DB
- Hopefully fixes #593
2019-01-19 09:35:32 +01:00
croneter
04da3572cb Fix rare KeyError if user is playing something somewhere else 2019-01-19 09:22:00 +01:00
croneter
d2f1a34092 Beta version bump 2.5.19 2019-01-17 20:36:13 +01:00
croneter
5f79e944ab Fix browsing to show from info dialog 2019-01-17 20:33:46 +01:00
croneter
5dd346b657 Fix crash on startup-sync due to missing albums 2019-01-17 19:58:07 +01:00
croneter
605318c1f0 Beta version bump 2.5.18 2019-01-17 19:33:14 +01:00
croneter
e4af13bbd5 Try even longer to write to Kodi database
- Partially fixes #593
2019-01-17 19:30:55 +01:00
croneter
3b9fce7470 Fix some items rarely not being synced 2019-01-17 18:05:02 +01:00
croneter
889b6094d9 Ignore playstate updates for full sync time stamps croneter committed 2019-01-17 17:49:00 +01:00
croneter
b86a6549ac Refresh Kodi view only once on full syncs 2019-01-17 17:36:26 +01:00
croneter
3e3aa49d0d Fix playback start: Don't lock databases when starting playback
- Partially fixes #599
2019-01-16 17:13:23 +01:00
croneter
4c2332ca99 Beta version bump 2.5.17 2019-01-14 18:24:28 +01:00
croneter
e255958b57 Fix playback not starting for really large libraries
- Partially fixes #599
2019-01-14 18:22:31 +01:00
croneter
332a2eca68 Beta version bump 2.5.16 2019-01-13 18:00:48 +01:00
croneter
4d17a9747f Fix KeyError due to malformed PMS messages
- Fixes #604
2019-01-13 17:57:48 +01:00
croneter
5cd5b81198 Fix to database parameter must be string 2019-01-13 17:30:47 +01:00
croneter
3fe0d51614 Revert "Fix to database parameter must be string"
This reverts commit 51cc5e8a2a.
2019-01-13 17:30:11 +01:00
croneter
51cc5e8a2a Fix to database parameter must be string
- Fixes #604
2019-01-13 17:25:52 +01:00
croneter
ef51a67779 Beta version bump 2.5.15 2019-01-13 14:57:00 +01:00
croneter
ce82725116 Further increase database sync resiliance
- Partially fixes #593
2019-01-13 14:51:48 +01:00
croneter
73bad8fa05 Increase number of attempts to write to Kodi DB
- Partially fixes #593
2019-01-13 14:29:35 +01:00
croneter
5a3ad09a04 Support for Kodi 18 Leia RC 5.2 2019-01-13 13:42:03 +01:00
croneter
1fccd23c4f Make PKC potentially compatible with several database schemas 2019-01-13 13:41:22 +01:00
croneter
4110c335c0 Beta version bump 2.5.14 2019-01-11 21:45:42 +01:00
croneter
0f289393c1 Fix rare OperationalError: Locked Database
- Partially fixes #593
2019-01-11 21:41:11 +01:00
croneter
c08a8f744e Beta version bump 2.5.13 2019-01-08 20:16:30 +01:00
croneter
251263eec7 Revert "Log locking of DB"
This reverts commit f5d98d66d4.
2019-01-08 20:14:48 +01:00
croneter
f5d98d66d4 Log locking of DB 2019-01-08 18:47:58 +01:00
croneter
2d19cd4d63 Fix playback not starting up
- Fixes #595
2019-01-08 18:46:45 +01:00
croneter
e590968d52 Fix Plex channels and watch later not working 2019-01-05 08:48:16 +01:00
croneter
b8ebed35f4 Beta version bump 2.5.12 2019-01-04 20:41:42 +01:00
croneter
262a2dda21 New option to not use Plex artwork 2019-01-04 20:38:45 +01:00
croneter
3d4ba1e165 Increase database resiliance with sqlite WAL mode 2019-01-04 18:02:58 +01:00
croneter
1f35caba54 Add-on paths: Fix resume if playback not initiated with PKC 2019-01-04 14:39:33 +01:00
croneter
df24ab88c9 Beta version bump 2.5.11 2019-01-01 19:19:09 +01:00
croneter
bd192e4b6a Direct Paths: Fix AttributeError for widgets
- Fixes #588
2019-01-01 16:53:50 +01:00
croneter
2e4e1e0aea Beta version bump 2.5.10 2019-01-01 13:26:46 +01:00
croneter
70632f1749 Enable Plex Hub listings to be used for widgets
- Fixes #585
2019-01-01 13:19:26 +01:00
croneter
56331bead5 Finally fix deleteting of items from PMS not working
- Fixes #587
2019-01-01 13:00:30 +01:00
croneter
0f95889b75 Revert "Increase database timeouts"
This reverts commit 74e8d1cc0f.
2019-01-01 12:51:38 +01:00
croneter
1ab9ff7790 Catch sqlite OperationalError for websocket messages
- Hopefully fixes #580
2019-01-01 12:51:33 +01:00
croneter
25f7c21018 Beta version bump 2.5.9 2018-12-30 22:13:31 +01:00
croneter
74e8d1cc0f Increase database timeouts
- Partially fixes #580
2018-12-30 22:10:36 +01:00
croneter
e4cb07df68 Close DB connections while caching images
- Partially fixes #580
2018-12-30 21:30:08 +01:00
croneter
2dff87dc4b Leia: fix resetting of videoplayer autoplay next item
- Fixes #466
2018-12-29 16:57:46 +01:00
croneter
134b76cb09 Ensure path replacement never contains trailing (back)slash
- Fixes #563
2018-12-29 16:13:59 +01:00
croneter
122bc56c31 Don't store identical show artwork for seasons
- Fixes #540
2018-12-29 15:35:17 +01:00
croneter
769f093492 Improve logging for seasons 2018-12-29 15:15:56 +01:00
croneter
6aa1a09b56 Compatibility with Kodi 18 RC 4 2018-12-29 11:29:10 +01:00
croneter
dc369a0453 Remove obsolete code 2018-12-28 17:13:56 +01:00
croneter
6d8885fae9 Optimize code 2018-12-27 12:10:41 +01:00
croneter
a9cf7eb294 New setting to escape paths e.g. for HTTP direct paths
- Fixes #555
2018-12-27 12:08:18 +01:00
croneter
376e66b5f0 Beta version bump 2.5.8 2018-12-25 19:15:43 +01:00
croneter
50686ae191 Fix database is locked
- Fixes #580
2018-12-25 19:12:49 +01:00
croneter
4ee828dfe9 Move function 2018-12-25 18:26:13 +01:00
croneter
6076da724b Fix video resuming from old resume point 2018-12-25 18:13:50 +01:00
croneter
4fb4643ac0 Add-on paths: don't fail playback with an empty video 2018-12-24 17:44:52 +01:00
croneter
a9f51f9ac4 Faster way to initialize playlists on the Plex side
- Partially fixes #578
2018-12-24 17:06:01 +01:00
croneter
323a4482e5 Improve logging 2018-12-24 13:19:40 +01:00
croneter
e9f49be7f3 Fix PKC recreating playlists too often 2018-12-23 13:01:50 +01:00
croneter
db6a7ba9b4 Shutdown playlist sync if necessary
- Partially fixes #578
2018-12-23 11:12:47 +01:00
croneter
6950a3d979 Increase logging 2018-12-23 10:20:28 +01:00
croneter
b0563f5dcc Beta version bump 2.5.7 2018-12-22 15:22:30 +01:00
croneter
6237d932d8 Improve Plex playQueue resiliance
- Partially fixes #566
2018-12-22 15:06:28 +01:00
croneter
16ff6a51f5 Increase database connection timeout when wiping 2018-12-22 14:33:52 +01:00
croneter
ff8bfd9523 Fix music DB not being wiped on database reset
- Fixes #575
2018-12-22 14:33:30 +01:00
croneter
79e4ff4509 Increase timeout for database connections
- Partially fixes #574
2018-12-22 14:20:56 +01:00
croneter
c95330c0f7 Beta version bump 2.5.6 2018-12-21 19:05:16 +01:00
croneter
7966dd5ee4 Fix resume for episodes for add-on paths 2018-12-21 19:02:36 +01:00
croneter
512479e47e Fix movies not showing up on switching PMS 2018-12-21 17:22:30 +01:00
croneter
825237dfed Close sync dialog if video playback starts 2018-12-21 17:00:51 +01:00
croneter
519c9675ae Finish full syncs during playbacks, don't start new ones 2018-12-21 16:53:53 +01:00
croneter
3b3d671f37 Remove obsolete import 2018-12-21 16:48:57 +01:00
croneter
5cf52aa2a3 Fix not all songs being synced 2018-12-21 16:33:31 +01:00
croneter
9b9d2be53d Only marking full sync as successful if that is indeed the case 2018-12-21 16:27:59 +01:00
croneter
9087f27f2a Optimize code 2018-12-21 16:03:56 +01:00
croneter
9a5239ab1d Optimize code 2018-12-21 15:37:16 +01:00
croneter
8803d3353c Fix some very few items not being synced 2018-12-21 15:36:14 +01:00
croneter
bd1b0cc533 Optimize code 2018-12-21 15:18:19 +01:00
croneter
5d6b8f1273 Fix many items not getting synced 2018-12-21 15:18:06 +01:00
croneter
9682d7a313 Return None as timestamp instead of 1970 timestamp 2018-12-20 12:52:45 +01:00
croneter
4fbcdcac7f Fix episodes not being synced to due a missing season
- Partially fixes #566
2018-12-14 19:51:30 +01:00
croneter
48b08b4ba4 Fix ValueError during sync due to missing Plex timestamp 2018-12-14 19:38:34 +01:00
croneter
1c25441e99 Fix AttributeError when a playlist disappeared
- Should fix #568
2018-12-11 14:57:43 +01:00
croneter
1156a4efa1 Don't show sync messages while Kodi is playing something
- Fixes #570
2018-12-11 12:54:14 +01:00
croneter
f6aab94d10 Beta version bump 2.5.5 2018-12-10 20:33:21 +01:00
croneter
b87ba4c753 Fix OperationalError and PKC not starting up 2018-12-10 20:32:59 +01:00
croneter
531b2dedb3 Beta version bump 2.5.4 2018-12-10 20:03:45 +01:00
croneter
1a46664051 Fix permanent missing library items if PMS failed to send a single response 2018-12-10 20:00:48 +01:00
croneter
97d829779f Fix OperationalError: enforce Kodi restart with clean DB once
- Fixes #570
2018-12-10 19:51:17 +01:00
croneter
d2e0479225 Fix switching PMS not recognizing when old PMS is selected 2018-12-10 19:19:46 +01:00
croneter
e0fbccabc8 Fix PKC not automatically connecting to changed PMS IP on startup 2018-12-10 07:39:40 +01:00
croneter
6eb36f0560 Remove message "Full library sync finished" 2018-12-10 07:35:46 +01:00
croneter
74e801cf4d Remove cProfile program metrics measurements 2018-12-10 07:32:57 +01:00
croneter
edbb99b2e2 Fix a couple of issues related to episodes
- Hopefully fixes #566
2018-12-10 07:23:25 +01:00
croneter
3632faeb67 Beta version bump 2.5.3 2018-12-09 19:22:17 +01:00
croneter
d406843e78 Fix Plex sections not showing up or disappearing 2018-12-09 19:21:02 +01:00
croneter
f3647aaf24 Beta version bump 2.5.2 2018-12-09 18:45:02 +01:00
croneter
3f173f9677 Force-Reboot Kodi immediately if sqlite PRAGMA WAL causes errors 2018-12-09 18:41:05 +01:00
croneter
22ea4a6a7c Fix FutureWarning 2018-12-09 18:38:15 +01:00
croneter
8ed61410ab Revert "Catch error if Kodi DB cannot be used in WAL mode"
This reverts commit e13273c5b8.
2018-12-09 17:48:42 +01:00
croneter
e13273c5b8 Catch error if Kodi DB cannot be used in WAL mode 2018-12-09 17:29:58 +01:00
croneter
07174a39f3 Revert "Catch error if Kodi DB cannot be used in WAL mode" 2018-12-09 17:28:45 +01:00
croneter
3a73b9de44 Fix AttributeError 2018-12-09 17:23:43 +01:00
croneter
f13950590e Increase database connection cache size 2018-12-09 17:19:16 +01:00
croneter
70cf483fe2 Do a Kodi library update after synching new/changed items 2018-12-09 16:16:26 +01:00
croneter
2baafa77bb Shut down download threads as soon as they're done 2018-12-09 16:15:37 +01:00
croneter
aeda4d97ba Remove obsolete code 2018-12-09 15:56:48 +01:00
croneter
adeeee7162 Fix wierd behavior upon switching to another PMS 2018-12-09 15:25:30 +01:00
croneter
1cc8cb7ad3 Force a full sync on switching Plex user 2018-12-09 14:36:13 +01:00
croneter
27d34167e9 Fix current sync progress for more than 2000 items 2018-12-09 14:23:01 +01:00
croneter
de90ab225f Revert "Do not cache artwork vor Kodi 18 Leia or later"
This reverts commit 9cc731e18f.
2018-12-09 14:16:33 +01:00
croneter
866412a720 Catch error if Kodi DB cannot be used in WAL mode 2018-12-09 14:00:26 +01:00
croneter
9cc731e18f Do not cache artwork vor Kodi 18 Leia or later
- Seems Kodi is doing a better job at it then PKC
- PKC caching could mess with Kodi threads trying to cache artwork that needs to show up immediately
2018-12-09 13:56:51 +01:00
croneter
c8405414d1 Add PKC settings status indication for caching 2018-12-09 13:52:26 +01:00
croneter
aa11761ae9 Correct string help text 2018-12-09 13:46:51 +01:00
croneter
01bbaaa90a Replace annoying sync message with PKC settings info 2018-12-09 13:45:09 +01:00
croneter
dac7cdd47e Fix KeyError when synching playlists
- Should fix #568
2018-12-09 13:10:15 +01:00
croneter
4f750379f1 Fix ImportError for Plex Companion gdm issues
- Partially fixes #568
2018-12-09 13:07:36 +01:00
croneter
80414ea0d4 Optimize sqlite transactions 2018-12-09 13:04:27 +01:00
croneter
d33ba7e502 Rewire library sync 2018-12-09 13:02:08 +01:00
croneter
f6415ae444 Optimize code 2018-12-09 09:14:45 +01:00
croneter
1ab1f18e07 Fix PKC version being recorded several times
- Partially fixes #566
2018-12-03 07:59:25 +01:00
croneter
28e4561461 Fix OperationalError: near "WHERE": syntax error
- Partially fixes #566
2018-12-03 07:54:27 +01:00
croneter
2753309149 Beta version bump 2.5.1 2018-12-02 17:50:02 +01:00
croneter
1864adf151 Fix OSError on resetting the database 2018-12-02 17:46:35 +01:00
croneter
d198808cfc Version bump for real, now 2018-12-02 17:20:43 +01:00
croneter
8216892427 Increase dependency for helper add-ons to 2.0.7 2018-12-02 17:15:22 +01:00
croneter
acc1b24480 Fix songs not appearing 2018-12-02 17:10:53 +01:00
croneter
260ff230c5 Don't check whether we need to delete "Artist" role for songs 2018-12-02 17:01:19 +01:00
croneter
64b8c522af Don't fill discography table for Kodi 18 2018-12-02 17:00:13 +01:00
croneter
f2ca67c418 Fix strMusicBrainzAlbumID 2018-12-02 16:32:36 +01:00
croneter
0400eec0c9 Fix iOrder of song_genre table being None instead of 0 2018-12-02 16:16:21 +01:00
croneter
a55c0c6ecb Add dateAdded for songs 2018-12-02 16:12:17 +01:00
croneter
59af1d09a3 Optimize code 2018-12-02 16:03:20 +01:00
croneter
8b3272aa7b Fix FutureWarning 2018-12-02 16:02:46 +01:00
croneter
89aa7bc1d9 Fix user select dialog not opening 2018-12-02 15:59:05 +01:00
croneter
b3b1d64484 Increase logging 2018-12-02 15:56:59 +01:00
croneter
f20341b983 Fix Plex.tv login not showing up upon installation 2018-12-02 15:52:49 +01:00
croneter
bb88c24e11 Increase logging 2018-12-02 15:46:15 +01:00
croneter
fb3f1d4669 Kodi 18: Ensure that we're setting music scan flag correctly upon boot
- Should fix #561
2018-12-02 15:25:54 +01:00
croneter
3c0bb49fbf Fix adding new album or artist to Plex in the background 2018-12-02 14:10:11 +01:00
croneter
e9f811f2cd Ignore PMS messages related to set/collections 2018-12-02 13:20:18 +01:00
croneter
2b3366923a Fix KeyError if a new set/collection is added to Plex 2018-12-02 13:19:13 +01:00
croneter
2e0d11a7bb Kodi 18: fix playback report for trailers 2018-12-02 11:11:24 +01:00
croneter
861af0f170 Further optimize sync speed 2018-12-02 10:13:27 +01:00
croneter
7fce226d47 Fix items getting deleted on subsequent sync 2018-12-01 18:50:52 +01:00
croneter
056463da55 Revert "Attempt to fix items getting deleted on second sync"
This reverts commit 459bd72299.
2018-12-01 18:43:47 +01:00
croneter
459bd72299 Attempt to fix items getting deleted on second sync 2018-12-01 18:43:42 +01:00
croneter
a776f940de Remove obsolete code 2018-12-01 16:52:22 +01:00
croneter
d972594553 Optimize DB access using transactions 2018-12-01 12:19:15 +01:00
croneter
07cf25b324 Get section overview xml asynchronously 2018-12-01 11:14:12 +01:00
croneter
41bbdbc206 Dedicated method for creating new actor 2018-12-01 11:09:59 +01:00
croneter
79785d0400 Fix FutureWarning 2018-12-01 09:13:23 +01:00
croneter
abfb386e13 Fix FutureWarning 2018-12-01 09:05:45 +01:00
croneter
024e0e5e09 Fix Typo 2018-11-30 08:05:23 +01:00
croneter
5b22cbcd52 Fix crash on switching Kodi profile
- Should fix #559
- Cannot use cElementTree with Kodi/XBMC reliably 😞
2018-11-28 09:13:38 +01:00
croneter
061dcb77e0 Revert "Catch cElementTree TypeError"
This reverts commit a10e3925d5.
2018-11-28 08:19:21 +01:00
croneter
a10e3925d5 Catch cElementTree TypeError
- Hopefully fixes #559
2018-11-26 20:44:36 +01:00
croneter
36d14ffdcf Optimize join() 2018-11-26 19:47:41 +01:00
croneter
9261a8b143 Increase queue size 2018-11-26 19:40:56 +01:00
croneter
ccdb76709e Fix OperationalError: Database is locked 2018-11-26 19:40:43 +01:00
croneter
0e335dd35e Fix MRO 2018-11-26 18:11:34 +01:00
croneter
55ec381bfe Fix sync not correctly being canceled 2018-11-26 17:58:15 +01:00
croneter
752a57c15e Fix OperationalError: no such table tvshows 2018-11-26 17:33:59 +01:00
croneter
4515559dff Fix AttributeError 2018-11-26 17:32:21 +01:00
croneter
a0e14ca2cb Fix TypeError 2018-11-26 17:23:35 +01:00
croneter
a7243d813d Force a full sync after manually changing PMS IP 2018-11-26 16:59:22 +01:00
croneter
0146c2e2b6 Force full sync on changing PMS 2018-11-26 16:58:25 +01:00
croneter
1f5eae8267 Fix PKC not connecting due to changed IP 2018-11-26 16:56:39 +01:00
croneter
27b2d4cbf2 Less logging 2018-11-26 16:17:03 +01:00
croneter
bd2c467183 Fix PKC crashing due to a restricted Python environment 2018-11-26 15:44:12 +01:00
croneter
6219ba6834 Fix TypeError 2018-11-26 07:58:12 +01:00
croneter
fa1dcdffaa Fix TypeErrors 2018-11-26 07:56:27 +01:00
croneter
b0a68b255e Fix NameError 2018-11-26 07:54:32 +01:00
croneter
a8fbcc6be4 Fix AttributeError 2018-11-26 07:50:38 +01:00
croneter
fada7f707f Move playback startup from thread to task 2018-11-26 07:48:45 +01:00
croneter
030c381f65 Ensure that only one thread accesses settings.xml
Avoids corruption of settings.xml
2018-11-26 07:35:19 +01:00
croneter
7d2f785a8d Move detection of context menu to main loop
- One thread less!
2018-11-26 07:31:36 +01:00
croneter
c4156cb865 Fix TypeError upon deactivating screensaver 2018-11-26 07:25:50 +01:00
croneter
a149d8de27 Fix widgets not receiving PKC information 2018-11-26 07:19:34 +01:00
croneter
0fe2de1705 Fix resetting of databases crashing 2018-11-25 21:07:25 +01:00
croneter
ded8a59dd7 Fix adding misisng artist on adding an album 2018-11-25 20:57:16 +01:00
croneter
202d33d7c0 Fix video library sometimes not updating 2018-11-25 20:36:54 +01:00
croneter
2ceb0cd7a2 Fix sync error when only an item is deleted in section 2018-11-25 20:31:40 +01:00
croneter
69e65f5ca6 Fix KeyError 2018-11-25 20:28:22 +01:00
croneter
697b66167c Fix deleting entire library sections 2018-11-25 20:15:38 +01:00
croneter
90e13cb8ee Fix TypeError upon removing entire section 2018-11-25 19:53:42 +01:00
croneter
67807d3eb7 Remove obsolete import 2018-11-25 19:50:50 +01:00
croneter
36eb5e9646 Force full sync after Plex user switch 2018-11-25 19:49:47 +01:00
croneter
b1f04c85a6 Code cleanup 2018-11-25 19:46:34 +01:00
croneter
3f42e24b7d Fix PKC not really starting upon first boot 2018-11-25 19:28:07 +01:00
croneter
81de319715 Fix more AttributeErrors 2018-11-25 17:29:21 +01:00
croneter
09f0492fa4 Fix AttributeErrors 2018-11-25 17:27:22 +01:00
croneter
9fd4a022a2 Fix RuntimeError: maximum recursion depth exceeded 2018-11-25 17:23:31 +01:00
croneter
a9bed6a3f8 Fix removal of movies 2018-11-25 17:21:41 +01:00
croneter
7670aa7a14 Fix OperationalError: Database is locked when video was deleted 2018-11-25 17:21:32 +01:00
croneter
6dc436da91 Rewire PKC startup and authentication mechanism 2018-11-25 17:03:19 +01:00
croneter
6d450b2be9 Fix TypeError 2018-11-24 14:06:21 +01:00
croneter
fb364a2275 Safety net for cleaning Kodi file table 2018-11-24 09:56:30 +01:00
croneter
3e754dfd1b Fix AttributeError 2018-11-24 09:54:57 +01:00
croneter
69b7f91542 New class to run functions as tasks 2018-11-24 09:53:37 +01:00
croneter
c25d6bee48 Clarify code 2018-11-24 09:52:36 +01:00
croneter
f32b5c1e71 Set fix limit of 6 worker threads 2018-11-24 09:52:25 +01:00
croneter
45fb84e697 Remove obsolete import 2018-11-23 17:25:28 +01:00
croneter
e87edb07f0 Fix PKC playQueue manager not picking up Plex ids correctly 2018-11-23 09:05:51 +01:00
croneter
8110c104f1 Less logging 2018-11-23 08:54:43 +01:00
croneter
02475bc8a6 Increase logging 2018-11-23 08:54:09 +01:00
croneter
5422b6d233 Fix AttributeError 2018-11-23 08:42:29 +01:00
croneter
3e41f63c62 Move xbmc.Player() 2018-11-23 08:41:05 +01:00
croneter
1b11c55d84 Fix AttributeError 2018-11-22 17:58:56 +01:00
croneter
d49ea8b383 Optimize code 2018-11-22 17:46:42 +01:00
croneter
9d6f729b21 Fix OperationalError: database is locked 2018-11-22 08:29:14 +01:00
croneter
d2922d61f5 Fix AttributeError 2018-11-22 08:04:41 +01:00
croneter
14c135a634 Fix OperationalError: no such column 2018-11-22 07:54:54 +01:00
croneter
5ad5bc1681 Fix SyntaxError 2018-11-22 07:46:15 +01:00
croneter
dcb10249a3 PEP8 2018-11-22 07:45:57 +01:00
croneter
7387b89d28 Fix OperationalError 2018-11-22 07:44:48 +01:00
croneter
fddd374b79 Fix some TypeErrors 2018-11-21 20:20:06 +01:00
croneter
c8b0e203ef Fix ImportError 2018-11-21 07:59:35 +01:00
croneter
ce191f6eeb Fix ProgrammingError: Cannot operate on a closed database 2018-11-20 19:23:42 +01:00
croneter
45b3c0e3f5 Fix potential TypeError if setting value is missing 2018-11-20 19:14:11 +01:00
croneter
0d22b72112 Fix TypeError 2018-11-20 19:10:26 +01:00
croneter
37ed6c2347 Remove obsolete code 2018-11-20 19:10:12 +01:00
croneter
b35cec106a Fix AttributeError due to missing method 2018-11-20 19:06:10 +01:00
croneter
f835e84d80 Fix context managers swallowing exceptions 2018-11-20 18:57:54 +01:00
croneter
5305d7bdfd Fix Syntax Error 2018-11-20 18:46:25 +01:00
croneter
2aadcbd198 Fix AttributeError 2018-11-20 18:39:18 +01:00
croneter
d668b3e640 Warn user and stop sync if crash occurs 2018-11-20 18:28:34 +01:00
croneter
0f740b1a02 Dedicated DB method to add video uniqueid 2018-11-20 18:24:43 +01:00
croneter
70a6ee5ed6 Dedicated DB method to add video rating_id 2018-11-20 18:18:52 +01:00
croneter
3acaad5663 Fix exceptions not being raised during sync 2018-11-20 18:08:51 +01:00
croneter
f68b4c8820 Fix AttributeError 2018-11-20 17:24:47 +01:00
croneter
f2df4fade6 Fix AttributeError 2018-11-20 17:17:52 +01:00
croneter
6fe0e23f53 Interrupt sleep if Kodi exits. Use Monitor's Player() 2018-11-20 16:58:25 +01:00
croneter
fff791e3d1 Remove obsolete code 2018-11-20 16:21:30 +01:00
croneter
e5c723d14f Remove obsolete code 2018-11-19 08:51:51 +01:00
croneter
f9f7b74ef3 Add user error message if Plex.tv does not provide users 2018-11-19 08:48:51 +01:00
croneter
a4dd5d8711 Prompt to login to plex.tv if no token available 2018-11-19 08:43:12 +01:00
croneter
3a514365ee Fix logging out also deleting Plex token 2018-11-19 08:17:49 +01:00
croneter
a045063769 Rather big change of PKC's plumbing 2018-11-18 14:59:17 +01:00
croneter
b2615c19bd Fix fanart download message showing up too often 2018-11-13 16:30:05 +01:00
croneter
a566dc566d Ensure that artwork caching thread suspends on STOP_SYNC 2018-11-13 16:02:54 +01:00
croneter
5c7968abdb Ensure that fanart sync closes DB connection during playback 2018-11-13 15:01:31 +01:00
croneter
789b214b50 Suspend fanart sync during playback 2018-11-13 14:54:54 +01:00
croneter
c168981a5b Optimize code 2018-11-13 14:51:10 +01:00
croneter
0c9e544e9e Suspend fanart sync if another sync is running 2018-11-13 14:48:25 +01:00
croneter
40e3a852a2 Fix AttributeError for songs 2018-11-13 14:42:48 +01:00
croneter
5a30b1c86a Fix download generator hanging 2018-11-13 14:38:38 +01:00
croneter
4ff2a8cb18 Try deleting temp DB files on DB wipe 2018-11-13 09:28:19 +01:00
croneter
e10d92cecb Cleanup music sync code 2018-11-13 09:02:34 +01:00
croneter
aafba74ccd PEP8 2018-11-13 07:47:37 +01:00
croneter
2d5e22c146 Update sync progress string 2018-11-12 16:37:56 +01:00
croneter
52dfd49080 More logging 2018-11-11 20:37:40 +01:00
croneter
bd1c2c3a0a More indicees for Kodi DB to increase sync speed 2018-11-11 20:13:10 +01:00
croneter
51745fd838 Optimize code 2018-11-11 20:11:19 +01:00
croneter
dfde9533d8 More indicees for Plex DB 2018-11-11 19:32:05 +01:00
croneter
ead799d38b More fixes 2018-11-11 19:22:32 +01:00
croneter
bc7fc4db1b Fixes to XML download iterator 2018-11-11 17:48:11 +01:00
croneter
7aec7fe776 Smarter caching for XML download generator 2018-11-11 17:14:40 +01:00
croneter
8535852699 Optimize code 2018-11-11 12:53:49 +01:00
croneter
18891a67fc Download entire xmls asynchronously, not just a piece 2018-11-11 10:47:18 +01:00
croneter
14359dd5f4 Revert "Change standard number of items to request from 200 to 400"
This reverts commit 46137763e0.
2018-11-11 09:45:21 +01:00
croneter
46137763e0 Change standard number of items to request from 200 to 400 2018-11-11 09:36:38 +01:00
croneter
54113a715b Fix AttributeError 2018-11-11 09:28:18 +01:00
croneter
afedf03ac9 New method for DB commit 2018-11-11 09:23:51 +01:00
croneter
6de47490c3 Revert "Wrap Kodi DB transactions explicitly"
This reverts commit be45d914d3.
2018-11-10 16:18:22 +01:00
croneter
be45d914d3 Wrap Kodi DB transactions explicitly 2018-11-10 16:09:06 +01:00
croneter
48f5d67d63 Change artwork for music items 2018-11-10 12:18:02 +01:00
croneter
da7d871af8 Add Plex type to sync progress bar 2018-11-09 15:05:17 +01:00
croneter
4fca9b56c4 Less logging 2018-11-09 14:42:09 +01:00
croneter
e624edc7ae Download PMS xml chunks asynchronously 2018-11-09 14:39:43 +01:00
croneter
136d242780 Change setting to slider 2018-11-09 14:37:40 +01:00
croneter
d16da6fa6f Fix OperationalError: database is locked 2018-11-09 11:19:32 +01:00
croneter
fd542936b8 Increase max number of threads to 30 2018-11-09 09:53:13 +01:00
croneter
0255bd3584 Rename method 2018-11-09 09:48:04 +01:00
croneter
ab718bae2a Optimize sync of video filename 2018-11-09 09:26:54 +01:00
croneter
76728d7319 Pass plex_type with section 2018-11-09 09:10:22 +01:00
croneter
5b85b2b71a Enable cProfiling for full sync 2018-11-09 09:08:04 +01:00
croneter
4e1f975647 Fix ValueError for queue on Kodi exit 2018-11-09 09:07:46 +01:00
croneter
0b81fcd39c Fix sync progress 2018-11-09 09:05:14 +01:00
croneter
7830a0e0e3 Let full_sync finish gracefully on Kodi exit 2018-11-09 08:56:57 +01:00
croneter
e37223f016 Fix full_sync advancing before a section was synced 2018-11-09 08:44:05 +01:00
croneter
c8e8e04697 Speed up sync of actor artwork 2018-11-09 08:02:04 +01:00
croneter
463d0540a4 Speed up sync with dedicated add_artwork method 2018-11-09 07:56:10 +01:00
croneter
096af4dc7c Simplify code 2018-11-09 07:42:47 +01:00
croneter
5f57b0af5f Greatly speed up sync with dedicated add_people method 2018-11-08 21:40:19 +01:00
croneter
a16eae143a Rewire kodi database access 2018-11-08 21:22:16 +01:00
croneter
150229061b Reduce Python DB overhead 2018-11-08 15:15:52 +01:00
croneter
2319d2fc9c Further speed up add_file 2018-11-07 13:37:37 +01:00
croneter
05c96dc1c6 Speed up idPath look-up 2018-11-07 13:33:16 +01:00
croneter
3d837fcf33 Enable PKC to use Kodi DB index "ix_files" 2018-11-07 13:31:38 +01:00
croneter
015451d2fd Speed up Plex API 2018-11-07 12:08:35 +01:00
croneter
8ef0af3ec7 Commit DB changes for every 200 items 2018-11-07 10:41:40 +01:00
croneter
0db29dd568 Get movie set information asynchronously 2018-11-07 10:37:32 +01:00
croneter
e460aea7e8 Do not catch exceptions if checksum calc goes awry 2018-11-07 07:46:20 +01:00
croneter
923c413921 Add fallback if checksum updatedAt is missing 2018-11-07 07:45:19 +01:00
croneter
009fc9937a Fix OperationalError when trying to wipe empty Plex DB 2018-11-06 20:33:05 +01:00
croneter
a5ce8e0a93 More thorough cleanup upon PKC first run 2018-11-06 20:24:36 +01:00
croneter
a81fd527c1 Be smart when wiping Kodi DBs: only wipe music if necessary 2018-11-06 20:22:30 +01:00
croneter
20f26364b8 Enforce plex id type int 2018-11-06 19:06:48 +01:00
croneter
c996e5c9be Allow float offset values 2018-11-06 19:03:01 +01:00
croneter
f767342e13 Introduce properties for playqueue items 2018-11-06 19:01:56 +01:00
croneter
30a1a0a70c Fix TypeErrors 2018-11-06 18:21:14 +01:00
croneter
ff6f056bca Fix Plex Companion seeking 2018-11-06 18:16:32 +01:00
croneter
a75afc109c Fix TypeError for selecting Plex media streams 2018-11-06 15:12:17 +01:00
croneter
440f399917 Fix TypeError 2018-11-06 14:13:28 +01:00
croneter
b08b3dc443 Fix TypeError 2018-11-06 14:11:47 +01:00
croneter
41ec923a30 Fix KeyErrors 2018-11-06 14:08:14 +01:00
croneter
892a1afdcb Fix KeyError 2018-11-06 13:50:46 +01:00
croneter
06d00b2a12 Use Kodi hostname as Plex Companion friendly name 2018-11-06 13:43:40 +01:00
croneter
124a289081 Increase version dependency for pkc movies and tv shows 2018-11-06 13:02:52 +01:00
croneter
52a225eb92 Fix setting int/float ListItem property 2018-11-06 13:02:10 +01:00
croneter
df6cb5718a Fix casting of int/float to str or unicode 2018-11-06 12:41:06 +01:00
croneter
2e8be9ec6b Fix AttributeErrors 2018-11-06 12:35:02 +01:00
croneter
b7fa4f2c7b Fix playback receiving string plex id, not int 2018-11-06 12:33:02 +01:00
croneter
1b94e23386 Fix worker quitting prematurely 2018-11-06 11:30:04 +01:00
croneter
3414a0a688 Fix SyntaxError 2018-11-06 11:20:20 +01:00
croneter
3123af6426 Use a dedicated full sync thread manager
- Prevents threads from restarting all the time
2018-11-06 11:17:21 +01:00
croneter
7180595e05 Correctly show the current sync item number 2018-11-05 18:31:59 +01:00
croneter
bc1ad1d998 Prevent error message upon DB reset 2018-11-05 18:31:26 +01:00
croneter
0fa8fe1144 Fix AttributeError on PKC DB reset 2018-11-05 18:18:46 +01:00
croneter
e4c9a7a259 Optimize sync loop 2018-11-05 18:13:57 +01:00
croneter
d44d6983b3 Remove obsolete code 2018-11-05 18:02:37 +01:00
croneter
07c4d64a84 Rewire artwork caching 2018-11-05 18:00:01 +01:00
croneter
e761567592 Rewire image caching thread 2018-11-05 15:23:51 +01:00
croneter
30d85eebc0 Fix FanartThread isSuspended 2018-11-05 14:34:34 +01:00
croneter
03fb6506f4 PEP8 2018-11-05 14:18:52 +01:00
croneter
58d34e75f0 Move start of image caching thread to sync 2018-11-05 14:17:48 +01:00
croneter
99d0fe7538 Add missing notification text 2018-11-05 14:13:25 +01:00
croneter
2ffd4491cf Fix sync isSuspended 2018-11-05 14:10:43 +01:00
croneter
f321baab82 Code cleanup 2018-11-05 14:03:19 +01:00
croneter
6e83a549d3 Enable user setting for number of sync threads 2018-11-05 14:03:07 +01:00
croneter
8205e19668 Let the Kodi library update after a full sync 2018-11-05 13:53:57 +01:00
croneter
0536a7c151 Don't spawn a separate full sync thread 2018-11-05 13:52:31 +01:00
croneter
0d5f59ab84 Less logging 2018-11-05 13:02:11 +01:00
croneter
b29b80ebe3 Less logging 2018-11-05 13:01:22 +01:00
croneter
b781a764ef Less logging 2018-11-05 13:00:26 +01:00
croneter
b7a6a58da3 Fix some more errors: Item not yet synced 2018-11-05 12:28:05 +01:00
croneter
263b7d7684 Fix TypeError 2018-11-05 12:24:18 +01:00
croneter
d2dd631b4b Fix some "Item not yet synced" errors for playstate updates 2018-11-05 12:19:08 +01:00
croneter
48b78fe73f Fix some errors: Item not yet synced 2018-11-05 11:49:00 +01:00
croneter
49b75d89c0 Less logging 2018-11-05 11:20:00 +01:00
croneter
16b59d7cbe Revert "Revert "Less logging""
This reverts commit fcb580a62a.
2018-11-05 11:17:21 +01:00
croneter
fcb580a62a Revert "Less logging"
This reverts commit a4bd3e469f.
2018-11-05 11:08:20 +01:00
croneter
a4bd3e469f Less logging 2018-11-05 11:07:57 +01:00
croneter
8ea787cc49 Less logging 2018-11-05 10:47:48 +01:00
croneter
35c87856fd Fix TypeError 2018-11-05 10:10:42 +01:00
croneter
417aefd588 Fix endless music sync loop 2018-11-05 09:52:18 +01:00
croneter
66ecaa155f Further optimize music sync 2018-11-05 09:51:21 +01:00
croneter
c9b6e67771 Optimize music sync 2018-11-05 09:39:33 +01:00
croneter
0b5fcb855c Fix OperationalError: no such column: Userrating 2018-11-04 17:47:40 +01:00
croneter
e8621acdf3 Fix OperationalError no such table 2018-11-04 17:44:06 +01:00
croneter
f8c6b21f51 Optimize logging 2018-11-04 17:33:55 +01:00
croneter
120a616331 Optimize code 2018-11-04 17:31:04 +01:00
croneter
0fa72faf61 Increase season sync resiliance 2018-11-04 17:28:22 +01:00
croneter
7f431f1923 Increase episode sync resiliance 2018-11-04 17:26:08 +01:00
croneter
2917bc982f Speed-up looking up of existing episodes 2018-11-04 17:02:01 +01:00
croneter
91ea8c8d00 Fix AttributeError 2018-11-04 17:00:34 +01:00
croneter
9245f44f70 Fix KeyError 2018-11-04 16:56:29 +01:00
croneter
41b5ec1b8e Fix KeyError 2018-11-04 16:55:24 +01:00
croneter
48a530b49a More hacking 2018-11-04 16:53:42 +01:00
croneter
2fcb43b9d9 PEP8 2018-11-03 18:47:51 +01:00
croneter
8bffc5a30f Fix OperationalError: Database is locked and heavily speed up sync 2018-11-03 18:40:11 +01:00
croneter
9c3c8b0d35 Fix log typo 2018-11-03 18:38:51 +01:00
croneter
bfef7a346e Refresh progress bar for every single item 2018-11-03 17:18:25 +01:00
croneter
85c5e15b91 Less logging 2018-11-03 17:10:14 +01:00
croneter
f32d1dde0f Less logging 2018-11-03 17:05:52 +01:00
croneter
b3eeabc9ad Fix AttributeError: __exit__ 2018-11-03 17:04:24 +01:00
croneter
2d0869b589 PEP8 2018-11-03 16:59:59 +01:00
croneter
80691861f8 Add lookup for audio types 2018-11-03 16:59:46 +01:00
croneter
b9a8d66e3d Fix KeyError 2018-11-03 16:56:51 +01:00
croneter
4cbad1f1f7 Enable notify for crashes for fanart 2018-11-03 16:53:56 +01:00
croneter
d0d6798bb1 Simplify code 2018-11-03 10:39:49 +01:00
croneter
335c0175a7 Fix KeyErrors 2018-11-03 10:37:10 +01:00
croneter
5673abc19b Rewire fanart sync 2018-11-03 10:36:37 +01:00
croneter
c967cfc8b1 Fix TypeError 2018-11-01 15:43:52 +01:00
croneter
835c047fb1 Rename logging 2018-11-01 15:43:43 +01:00
croneter
aabdf15072 Rewire websocket sync 2018-11-01 15:43:27 +01:00
croneter
146290c03e Switch to xbmc.abortRequested to terminate PKC 2018-10-31 15:35:44 +01:00
croneter
28a38c63a1 Fix OperationalError: near "1": syntax error 2018-10-29 13:35:46 +01:00
croneter
fbaccdf4bf Fix TypeError 2018-10-29 13:26:58 +01:00
croneter
ce002a0fa8 Improve the checking of an item's checksum 2018-10-29 13:26:14 +01:00
croneter
8c3764e8ad Fix OperationalError: no such table: artist 2018-10-29 13:21:35 +01:00
croneter
3d77bd64d1 Add missing method 2018-10-28 17:02:06 +01:00
croneter
01fc08b027 Fix TypeError: 'InitNewSection' object has no attribute 2018-10-28 16:50:00 +01:00
croneter
facf2d5e2d PEP8 2018-10-28 16:17:20 +01:00
croneter
17a7d306ae Fix AttributeError 2018-10-28 16:14:37 +01:00
croneter
8f86fc1038 Fix AttributeError 2018-10-25 18:29:47 +02:00
croneter
60f7d0fce2 Don't cast to unicode 2018-10-25 18:28:41 +02:00
croneter
0dfcebbee3 Fix TypeError 2018-10-25 18:17:00 +02:00
croneter
c10afd1920 Fix AttributeError 2018-10-25 18:16:01 +02:00
croneter
7640f1e2d2 Fix process_metadata and get_metadata 2018-10-25 18:14:35 +02:00
croneter
d5f4ad3e62 Fix AttributeError for Tasks 2018-10-25 17:50:59 +02:00
croneter
62c07b2ee0 Remove obsolete code 2018-10-25 16:07:56 +02:00
croneter
b7aedca7fa Add missing method 2018-10-25 16:07:34 +02:00
croneter
3a411cc36b Fix TypeError 2018-10-25 15:57:12 +02:00
croneter
4b7eace923 Fix AttributeError 2018-10-25 15:55:46 +02:00
croneter
eee4554213 Fix KeyErrors 2018-10-25 15:54:22 +02:00
croneter
587df50c54 Fix AttributeError 2018-10-25 13:27:12 +02:00
croneter
dfccefe2e8 Fix UnboundLocalError 2018-10-25 13:25:25 +02:00
croneter
14f7b56b08 Fix TypeError 2018-10-25 13:22:34 +02:00
croneter
ff73318157 Less logging 2018-10-25 13:21:24 +02:00
croneter
59dc295dc8 Fix AttributeError 2018-10-25 13:20:46 +02:00
croneter
9bab18367c Fix turning generator into list 2018-10-25 13:19:46 +02:00
croneter
a060b1fcaa Simplify call of SectionItems download 2018-10-25 13:05:26 +02:00
croneter
91533aa89f Fix time sync not downloading items 2018-10-25 13:02:36 +02:00
croneter
a603fbadca Drop Plex tables entirely on DB reset; no further version check needed 2018-10-25 12:55:25 +02:00
croneter
c2efe0d57f Optimize sync startup 2018-10-25 12:46:57 +02:00
croneter
2a97678ba4 Compare Plex DB version separately 2018-10-25 08:43:47 +02:00
croneter
e0fdfac063 Check Plex DB version and force-reset if necessary 2018-10-24 18:40:44 +02:00
croneter
3558182b7e Fix AttributeError: 'super' object has no attribute 2018-10-24 18:08:00 +02:00
croneter
a9138cbd71 Fix OperationalError 2018-10-24 18:07:51 +02:00
croneter
e74a4ba2e9 Fix ImportError 2018-10-24 17:59:41 +02:00
croneter
ae37e21aeb Fix AttributeError 2018-10-24 17:56:59 +02:00
croneter
b94952ba4a Fix ImportError 2018-10-24 17:55:56 +02:00
croneter
c7e7ac65a4 Fix ImportError 2018-10-24 17:55:04 +02:00
croneter
31a0939b42 Fix SyntaxError 2018-10-24 17:54:10 +02:00
croneter
4edeaba365 Fix ImportError 2018-10-24 17:53:20 +02:00
croneter
8799ec8592 Fix IndentationError 2018-10-24 17:51:43 +02:00
croneter
3bc1caebca Require database reset 2018-10-24 17:23:38 +02:00
croneter
842df1773f Rename librarysync 2018-10-24 17:19:36 +02:00
croneter
4246711b1e Remove old plexdb_functions 2018-10-24 17:17:02 +02:00
croneter
e7899d656d Remove old itemtypes 2018-10-24 15:23:38 +02:00
croneter
205dc3fab8 Rewire llibrary sync, part 7 2018-10-24 15:20:25 +02:00
croneter
f520cebf66 Rewire llibrary sync, part 6 2018-10-24 10:57:52 +02:00
croneter
2f96749fc7 Rewire llibrary sync, part 5 2018-10-24 07:08:32 +02:00
croneter
23dada9fe5 Rewire llibrary sync, part 4 2018-10-23 13:54:09 +02:00
croneter
35a25a7f15 More hacking 2018-10-21 18:32:11 +02:00
croneter
e935b7c97b Rewire llibrary sync, part 3 2018-10-21 16:56:13 +02:00
croneter
3f4c43e373 Rewire llibrary sync, part 2 2018-10-21 12:03:21 +02:00
croneter
e6692a9012 Rewire llibrary sync, part 1 2018-10-20 14:49:04 +02:00
croneter
3c6979813b Add download generator 2018-10-14 19:59:11 +02:00
croneter
cabae3f6d4 Beta version bump 2018-10-14 12:26:10 +02:00
croneter
02b20a9b74 Catch ParseError from defusedxml correctly 2018-10-14 12:15:09 +02:00
croneter
76fbf3ac83 Fix cElementTree TypeError: cannot serialize 2018-10-14 12:14:41 +02:00
croneter
36c627651e Fix cElementTree TypeError: cannot serialize 2018-10-14 12:13:52 +02:00
croneter
6021407929 Revert "Optimize code"
This reverts commit c5e1d7a7df.
2018-10-14 12:01:15 +02:00
croneter
e45a133f51 Fix cElementTree TypeError: cannot serialize 2018-10-14 11:48:44 +02:00
croneter
c5e1d7a7df Optimize code 2018-10-14 11:47:33 +02:00
croneter
8199c2ce5b Fix PEP-8 2018-10-14 11:12:48 +02:00
croneter
c2c13b715d Fix typo 2018-10-14 11:12:08 +02:00
croneter
740cc5a6ff Fix NameError 2018-10-13 20:28:55 +02:00
croneter
c5741c7225 Use xml.etree.cElementTree whenever possible to avoid memory leaks 2018-10-13 20:17:16 +02:00
croneter
b7a9a1eca9 Beta and stable version bump 2.4.9 2018-10-13 14:45:32 +02:00
croneter
770b3704e8 Fix Kodi crashing due to PKC memory leak
- Fixes #531
2018-10-13 14:43:44 +02:00
croneter
90688b0e8e Beta and stable version bump 2.4.8 2018-10-12 12:09:10 +02:00
croneter
85d89f2fa2 Update changelog 2018-10-10 20:07:12 +02:00
croneter
c7511e90a7 Beta version bump 2.4.7 2018-10-10 20:03:52 +02:00
croneter
6be1f40373 Try to fix PKC for Enigma 2
- Hopefully fixes #545
2018-10-10 19:26:35 +02:00
croneter
d09933a68a PEP8 2018-10-10 19:19:30 +02:00
croneter
25675a9136 Optimize resetting of Kodi and Plex databases
- Fixes #527
2018-10-10 19:18:06 +02:00
croneter
06f1045656 Beta version bump 2.4.6 2018-10-07 17:54:05 +02:00
croneter
c9341169c3 Increase logging 2018-10-07 17:52:38 +02:00
croneter
27e92afe02 Make sure we retain a dummy first music artist entry
- Hopefully fixes #527
2018-10-07 17:47:32 +02:00
croneter
594e908508 Fix PKC not starting up on Enigma
- Fixes #545
2018-10-07 17:12:30 +02:00
croneter
6718182411 Fix sync issues if video lies in root of file system
- Fixes #544
- Manual Kodi database reset is necessary
2018-10-07 17:07:45 +02:00
croneter
b3f222f117 Beta version bump 2.4.5 2018-10-06 15:51:10 +02:00
croneter
8d9d135595 Wipe Kodi database on first PKC run
- Fixes #543
2018-10-06 15:47:41 +02:00
croneter
ab81c88b8f Fix flickering Kodi screen 2018-10-06 14:45:01 +02:00
croneter
8e1b3444ac Rewire Kodi library refreshs 2018-10-06 14:40:14 +02:00
croneter
9f35e1d99e Fix playback not starting up at all 2018-10-06 13:30:43 +02:00
croneter
a0f99951a2 Beta version bump 2.4.4 2018-10-04 19:49:45 +02:00
croneter
0d0a5948ac Fix rare case when playback would not start-up 2018-10-04 19:48:13 +02:00
croneter
be57db9200 Increase logging 2018-10-04 19:45:44 +02:00
croneter
a20205f2b1 Beta and stable version bump 2.4.3 2018-09-29 16:54:09 +02:00
croneter
7b4e7cbb22 Fix Kodi addons throwing jsonrpc errors (database reset needed)
- Fixes #539
2018-09-29 16:51:51 +02:00
croneter
7a3016a31b Beta and stable version bump 2.4.2 2018-09-27 19:12:33 +02:00
croneter
baf4f5dc44 Beta version bump 2.4.1 2018-09-23 16:06:31 +02:00
croneter
c6e1a7029a Fix numbering of already existing playlist files 2018-09-23 16:03:50 +02:00
croneter
5fabaf6a8e Hopefully fix endless playlist sync loops 2018-09-23 15:06:41 +02:00
croneter
21fb1ad015 Ensure shows are deleted before seasons before episodes 2018-09-23 13:52:08 +02:00
croneter
35d0f6a49d Fix library sync crash on deleting episode with missing season 2018-09-23 13:45:57 +02:00
croneter
cbcdc74a8c Fix Typo 2018-09-22 12:07:30 +02:00
croneter
88ebf718d5 Optimize logging 2018-09-22 12:06:02 +02:00
croneter
4a98bf27a6 Beta and stable version bump 2.4.0 2018-09-22 11:49:11 +02:00
croneter
406d83bf56 Beta version bump 2.3.14 2018-09-20 18:52:21 +02:00
croneter
e38d50bc54 Move PKC info label on user selection screen 2018-09-18 16:34:49 +02:00
croneter
e3328ad061 Switch to Plex style dialogs 2018-09-18 16:26:40 +02:00
croneter
e9e1b7b7de Revert "Change optionsdialog import behavior"
This reverts commit ad444a5da0.
2018-09-18 16:20:24 +02:00
croneter
ad444a5da0 Change optionsdialog import behavior 2018-09-18 14:18:07 +02:00
croneter
759ea44ad4 Include PKC info in plex.tv dialogs 2018-09-18 11:38:03 +02:00
croneter
f8218ffa4c Include PKC info in user selection dialog 2018-09-18 11:14:54 +02:00
croneter
6666d3fc3a Fix AttributeError on forcing texture caching 2018-09-17 18:13:33 +02:00
croneter
9efaa11830 Beta version bump 2.3.13 2018-09-16 19:04:58 +02:00
croneter
075f3638ac Update readme 2018-09-16 19:01:55 +02:00
croneter
e1c01b184b Don't show a pre-sign-in dialog 2018-09-16 17:29:18 +02:00
croneter
dd62cded43 Simplify code 2018-09-16 17:18:46 +02:00
croneter
c6073e2426 Fix user not being able to close dialog 2018-09-16 17:18:37 +02:00
croneter
1fe244e8a6 Use Plex dialog 2018-09-16 16:13:08 +02:00
croneter
c4f6a441b6 Fix Info screen not returning 2018-09-16 16:08:44 +02:00
croneter
c7bb333e97 Fix AttributeError 2018-09-16 14:02:27 +02:00
croneter
4cf191cbb4 Switch paths 2018-09-16 14:00:52 +02:00
croneter
1a4135c6a4 Fix add-on settings not opening on installation 2018-09-16 13:35:07 +02:00
croneter
9dc86c9731 Fix UnicodeDecodeError for PMS with non ASCII chars 2018-09-16 13:33:20 +02:00
croneter
a82fda85ca New Plex.tv sign-in dialogs 2018-09-16 13:25:15 +02:00
croneter
3110d8c0ee Use Plex dialogs for initial PKC set-up 2018-09-15 16:53:06 +02:00
croneter
93fd45a4e1 Add new Plex yes-no dialog 2018-09-15 16:30:17 +02:00
croneter
029ea93268 Translate Plex info dialog button text 2018-09-15 16:15:14 +02:00
croneter
038bce1a27 Use Plex dialog 2018-09-15 16:12:10 +02:00
croneter
a7791bad0a Don't show a warning if user aborted user selection 2018-09-15 16:08:51 +02:00
croneter
2b684941b7 Merge branch 'savage93-master' into beta-version 2018-09-15 15:55:27 +02:00
croneter
1bf98cb27e Greatly speed up deleting of items on the Kodi side 2018-09-15 15:50:59 +02:00
croneter
3088078ff8 Fix empty pin entry field showing up 2018-09-15 15:46:06 +02:00
croneter
b5524d1206 Optimize code 2018-09-15 15:16:59 +02:00
croneter
0147e29cef Only try user login once 2018-09-15 15:15:28 +02:00
croneter
82a7e021ef Make home button close user selection dialog 2018-09-15 15:07:57 +02:00
croneter
6a32fd2c33 Enlargen home button 2018-09-15 14:24:14 +02:00
croneter
66d5ae1347 Replace power button with home button 2018-09-15 13:51:45 +02:00
savage93
e26753cc0c Translated recently added strings 2018-09-14 20:32:55 +02:00
croneter
98e38ae9a8 Add Plex dialog to switch users 2018-09-10 20:53:46 +02:00
croneter
233f6065ee Add Plex artwork and xml 2018-09-09 12:02:57 +02:00
croneter
b29e07846f Safely parse XMLs using defusedxml 2018-09-05 17:36:38 +02:00
croneter
0933dea407 Simplify code 2018-09-04 16:54:06 +02:00
croneter
ea7f730264 Merge branch 'beta-version' 2018-09-04 16:48:53 +02:00
croneter
2200ae7535 Update readme 2018-09-04 16:47:20 +02:00
croneter
d63f976da4 Update docstrings 2018-09-04 16:43:16 +02:00
croneter
7fe12968a1 Fix PKC trying to sync audio playlists even when audio sync disabled 2018-09-04 16:32:39 +02:00
croneter
f591c6e4de Merge branch 'beta-version' 2018-09-02 20:44:45 +02:00
croneter
9db059845c Beta and stable version bump 2.3.12 2018-09-02 20:44:23 +02:00
croneter
cda881c25c Fix potential sync crash 2018-09-02 20:34:44 +02:00
croneter
e14994cf74 Fix Kodi hanging if media stream selection is aborted 2018-09-02 19:40:56 +02:00
croneter
4b3f641f25 Revert "Fix Kodi crash by committing to DB frequently"
This reverts commit 535163b675.
2018-09-02 18:18:45 +02:00
croneter
80d079312a Merge branch 'beta-version' 2018-08-30 12:29:56 +02:00
croneter
986981527e Beta version bump 2.3.11 2018-08-30 12:29:36 +02:00
croneter
535163b675 Fix Kodi crash by committing to DB frequently
- Hopefully fixes #531
2018-08-30 12:27:16 +02:00
croneter
7601b7dad0 Merge branch 'beta-version' 2018-08-29 16:59:37 +02:00
croneter
04558ae73c Beta and stable version bump 2.3.10 2018-08-29 16:58:54 +02:00
croneter
db5857f1aa Update translations 2018-08-29 16:55:16 +02:00
croneter
456c0401cd Compatibility with Kodi Leia Beta 1 2018-08-29 16:33:36 +02:00
croneter
40eeae18a9 Merge branch 'beta-version' 2018-08-23 18:41:26 +02:00
croneter
72a33060c4 Add Hits! to readme 2018-08-23 18:28:57 +02:00
croneter
c2d9470fe9 Remove obsolete command 2018-08-23 15:49:01 +02:00
croneter
fd4a0e8e0d Merge branch 'beta-version' 2018-08-23 15:42:11 +02:00
croneter
d10990b4e0 Beta version bump 2.3.9 2018-08-23 15:41:51 +02:00
croneter
9a8fcbb8a5 Fix playback not resuming (Kodi 18 ignores listitem "StartOffset")
- Fixes #525
2018-08-23 15:23:38 +02:00
croneter
229a04e65b Less logging 2018-08-23 15:18:55 +02:00
croneter
e3a209c24b Fix playerid not being retrieved for Kodi 18 2018-08-23 15:16:23 +02:00
croneter
ce4ca71766 Fix typos 2018-08-23 13:33:08 +02:00
croneter
ac2a57b28d Prefer local trailers; new setting to list extras instead of playing trailer
- Fixes #515
2018-08-13 20:26:36 +02:00
croneter
abca9c32f9 Update translations 2018-08-10 08:49:52 +02:00
croneter
7b437a4878 Merge branch 'beta-version' 2018-08-09 07:47:17 +02:00
croneter
904e9fb467 Stable and beta version bump 2.3.8 2018-08-09 07:46:54 +02:00
croneter
3b60e6ffd2 Fix Typo 2018-08-09 07:45:10 +02:00
croneter
d48c0b5ce5 Merge branch 'beta-version' 2018-08-07 20:07:02 +02:00
croneter
21a4703419 Beta version bump 2.3.7 2018-08-07 20:06:28 +02:00
croneter
3bd9b3b5a4 Force-deactivate playlist sync for Microsoft UWP for Kodi 18
- Fixes #521
2018-08-07 20:02:44 +02:00
croneter
3fd40b64c6 Fix library sync crash due to exotic playlist characters
- Fixes #523
2018-08-07 18:11:23 +02:00
croneter
06f008b8b3 Merge branch 'beta-version' 2018-08-06 07:55:46 +02:00
croneter
bccd79da38 Beta version bump 2.3.6 2018-08-06 07:54:29 +02:00
croneter
5003fd87c9 Fix PKC not starting by decoupling watchdog/subprocess modules
- Fixes #521
2018-08-06 07:53:30 +02:00
croneter
7a2752f4b0 Merge branch 'beta-version' 2018-08-05 18:15:30 +02:00
croneter
632dae37be Beta version bump 2.3.5 2018-08-05 18:14:56 +02:00
croneter
57e064efa2 Log whether playlist sync is activated 2018-08-05 18:13:16 +02:00
croneter
53c10b0847 Fix PKC not starting by importing playlist module only when sync enabled
- Fixes #521
2018-08-05 18:10:18 +02:00
croneter
d8386c4cc0 Merge branch 'beta-version' 2018-08-05 14:45:51 +02:00
croneter
04da98bcc6 Beta version bump 2.3.4 2018-08-05 14:45:32 +02:00
croneter
11a66a8465 Fix playback sometimes not starting and UnicodeEncodeError for logging
- Fixes #520
2018-08-05 14:44:24 +02:00
croneter
7e9c5bbd62 Merge branch 'beta-version' 2018-08-04 15:13:58 +02:00
croneter
a46fc22fb2 Beta and stable version bump 2.3.3 2018-08-04 15:13:18 +02:00
croneter
f5ea8cc3ec Choose trailer if several are present (DB reset required)
- Fixes #515
2018-08-04 15:11:21 +02:00
croneter
e66b4a280c Merge branch 'beta-version' 2018-08-03 20:47:19 +02:00
croneter
8e5f6d5d6d Beta and stable version bump 2.3.2 2018-08-03 20:46:46 +02:00
croneter
3f6f557dc3 Fix casting to PKC failing 2018-08-03 20:45:10 +02:00
croneter
899c029ab0 Merge branch 'beta-version' 2018-08-03 18:41:48 +02:00
croneter
b7faecb46c Beta and stable version bump 2.3.1 2018-08-03 18:41:14 +02:00
croneter
82a1771473 Update readme 2018-08-03 18:39:26 +02:00
croneter
8322609ef9 Fix library sync crashing due to Plex photo albums
- Fixes #517
2018-08-03 18:37:16 +02:00
croneter
396e594292 Merge branch 'beta-version' 2018-08-01 14:19:29 +02:00
croneter
90b66882c5 Update changelog 2018-08-01 14:19:07 +02:00
croneter
d8fc7bed59 Merge branch 'beta-version' 2018-08-01 14:07:57 +02:00
croneter
2c0499107b Update changelog for PKC Kodi metadata 2018-08-01 14:04:49 +02:00
croneter
065e723517 Include stable version bumps in changelog.txt 2018-08-01 14:03:21 +02:00
croneter
3c6096896a Beta and stable version bump 2.3.0 2018-08-01 14:02:14 +02:00
croneter
43a48ffcbe Merge branch 'beta-version' 2018-07-30 13:22:57 +02:00
croneter
5a4319c548 Beta version bump 2.2.18 2018-07-30 13:22:36 +02:00
croneter
5eafcbafb1 Move PKC playlist shortcut 2018-07-30 13:20:40 +02:00
croneter
ce30a3f03f Fix PKC tv show node "all" 2018-07-30 13:04:51 +02:00
croneter
bb9702550e Merge branch 'beta-version' 2018-07-27 16:04:57 +02:00
croneter
a5614b5d34 Beta version bump 2.2.17 2018-07-27 16:03:35 +02:00
croneter
5968e845d2 Access Plex Hubs. Listing will be different depending on Kodi section! 2018-07-27 16:01:27 +02:00
croneter
04725a8aca Fix year for songs missing
- Fixes #514
2018-07-27 14:49:30 +02:00
croneter
1ca9099a0e Fix Plex extras not playing
- Fixes #515
2018-07-27 14:05:44 +02:00
croneter
f0db5a82f8 Fix rare library sync crash
- Fixes #513
2018-07-27 13:38:41 +02:00
croneter
352cbece62 Merge branch 'beta-version' 2018-07-27 07:50:31 +02:00
croneter
633f23c02e Beta version bump 2.2.16 2018-07-27 07:49:40 +02:00
croneter
25d80521c7 Enable Kodi libraries for Plex Music libraries 2018-07-24 21:04:31 +02:00
croneter
777b9e15e4 Delete obsolete code 2018-07-19 15:30:52 +02:00
croneter
ad3c0a51d5 Increase logging for browsing 2018-07-19 15:07:08 +02:00
croneter
ccd953704e Only show Plex libraries in the applicable Kodi media category 2018-07-19 15:02:21 +02:00
croneter
1721aad580 Turn private into public method 2018-07-19 14:54:46 +02:00
croneter
232d3a3199 Optimize code 2018-07-19 14:53:16 +02:00
croneter
30f7cdf701 Less logging 2018-07-19 14:52:07 +02:00
croneter
6957b9a522 Retrieve strings from Kodi if no string is found for PKC 2018-07-19 13:54:36 +02:00
croneter
6e6d6cc110 New Playlists menu item for video libraries 2018-07-17 13:48:09 +02:00
croneter
abea138ac6 Merge branch 'beta-version' 2018-07-16 16:11:13 +02:00
croneter
cc01f3009c Beta version bump 2.2.15 2018-07-16 16:10:57 +02:00
croneter
38f5f9c694 Fix ImportError on first PKC run 2018-07-16 16:06:20 +02:00
croneter
27c3e2761e Merge branch 'beta-version' 2018-07-15 19:22:56 +02:00
croneter
976cb506b9 Beta version bump 2.2.14 2018-07-15 19:22:30 +02:00
croneter
06bfb95623 Hopefully fix playlist sync loops 2018-07-15 19:20:51 +02:00
croneter
59a64d227d Less logging 2018-07-13 07:31:21 +02:00
croneter
eb975dc5f5 Merge branch 'beta-version' 2018-07-12 19:02:01 +02:00
croneter
551c06ac7c Beta version bump 2.2.13 2018-07-12 19:01:46 +02:00
croneter
4e0429fcc9 Fix switching to __future__ module
- Fixes #507
2018-07-12 18:46:02 +02:00
croneter
141234c8b8 Fix "Prefer Kodi Artwork" toggle doing the exact opposite 2018-07-12 16:34:35 +02:00
croneter
726addd117 Fix "Prefer Kodi artwork" setting not being visible 2018-07-12 16:23:33 +02:00
croneter
8275c943b8 Merge branch 'beta-version' 2018-07-12 11:04:58 +02:00
croneter
7192a2a08b Beta version bump 2.2.12 2018-07-12 10:56:41 +02:00
croneter
61e185ae4f Improve logging 2018-07-12 10:50:45 +02:00
croneter
109fafcf4c Improve logging 2018-07-12 09:24:51 +02:00
croneter
1f5ce87bdc Improve logging for playlists 2018-07-12 07:58:48 +02:00
croneter
b8286c9b14 Fix KeyError on playlist full sync 2018-07-12 07:49:48 +02:00
croneter
c0d78bd273 Refactor code 2018-07-11 21:24:27 +02:00
croneter
e0108eeb89 Rewire partial playlist sync, part 2 2018-07-10 21:19:08 +02:00
croneter
f2d782c15a Fix slow sync. Fix endless sync of corrupted PMS elements
- Fixes #135
- Fixes #506
2018-07-10 20:37:26 +02:00
croneter
1626436094 Fix FutureWarning 2018-07-10 20:28:06 +02:00
croneter
ad54059289 Rewire partial playlist sync, part 1 2018-07-09 21:11:51 +02:00
croneter
8bb6236c4a Merge branch 'beta-version' 2018-07-08 13:27:41 +02:00
croneter
873601ec5a Beta version bump 2.2.11 2018-07-08 13:26:59 +02:00
croneter
2e0b23e299 Fix OnDeck widget for Direct Paths
- Fixes #505
2018-07-08 13:24:25 +02:00
croneter
c557cbdb93 Fix Plex Companion crashing when connected to Plex Web 2018-07-08 12:53:03 +02:00
croneter
bd34ee20e3 Revert "Fix Plex Companion crashing when connected to Plex Web"
This reverts commit baebd11825.
2018-07-08 12:46:22 +02:00
croneter
0d601d7309 Fix Plex Companion crash when connected to Plex Web playing playlist music 2018-07-08 12:37:38 +02:00
croneter
baebd11825 Fix Plex Companion crashing when connected to Plex Web 2018-07-08 12:28:28 +02:00
croneter
99ea9dd61f Improve Plex playback report when playing music playlist 2018-07-08 12:13:32 +02:00
croneter
9e7868141d Improve reliability in Kodi song playback 2018-07-08 11:52:59 +02:00
croneter
fcb5e131dc Less logging 2018-07-08 11:30:02 +02:00
croneter
e1c9690b57 Catch some errors if user mixes audio and video in Kodi playqueue
- Plex does not support mixed playqueues nor playlists
2018-07-08 11:08:30 +02:00
croneter
321930b2f0 Merge branch 'beta-version' 2018-07-07 19:34:27 +02:00
croneter
6d571a9bb5 Beta version bump 2.2.10 2018-07-07 19:34:01 +02:00
croneter
662dbba2e8 Fix playlist sync settings not disappearing 2018-07-07 19:32:39 +02:00
croneter
6f38472b17 Fix FutureWarning 2018-07-07 19:16:33 +02:00
croneter
70d809f179 Add some safety nets for playlist sync 2018-07-07 19:10:52 +02:00
croneter
e015770dd1 Optimize code 2018-07-07 18:59:40 +02:00
croneter
33afc448fd Clarify some comments 2018-07-07 18:57:09 +02:00
croneter
641520dcbb Optimize code 2018-07-07 18:32:11 +02:00
croneter
d44e782543 Fix playlists getting recreated and deleted in an endless loop 2018-07-07 18:21:50 +02:00
croneter
0166aaf7ba Decrease filesystem safety margin to 1 second 2018-07-06 21:48:07 +02:00
croneter
c29b47319f Drop directory filesystem events immediately 2018-07-06 21:44:08 +02:00
Croneter
5926168427 Merge branch 'beta-version' 2018-07-06 09:04:36 +02:00
Croneter
88c7ee3d73 Beta version bump 2.2.9 2018-07-06 09:03:46 +02:00
Croneter
44bbcddbdf Hopefully fix Kodi and Plex playlists getting out of sync
- Implement a special Watchdog observer that will wait for <timeout> AFTER a filesystem event has been received
2018-07-06 09:01:46 +02:00
Croneter
4fe95fdf12 Fix typo 2018-07-05 17:55:52 +02:00
Croneter
080e9184cc Fix and optimize startup of playlist sync 2018-07-05 16:38:06 +02:00
Croneter
f9571f009b Hide certain playlist settings under certain conditions 2018-07-05 16:12:34 +02:00
Croneter
248cbe2c90 Fix errors in Kodi log
- Fixes #504
2018-07-05 13:55:38 +02:00
Croneter
2bed767a76 Merge branch 'beta-version' 2018-07-05 13:26:03 +02:00
Croneter
197bee9348 Beta version bump 2.2.8 2018-07-05 13:24:25 +02:00
Croneter
038e557e7b Fix UnboundLocalError 2018-07-05 13:10:42 +02:00
Croneter
e28a7af7ed Catch exception 2018-07-05 13:03:18 +02:00
Croneter
f6a0b70ca4 Deduplication 2018-07-05 12:59:13 +02:00
Croneter
89d55b8b60 Fix hard PKC not working (OSError: no such file) 2018-07-05 12:52:02 +02:00
Croneter
e62b909a75 Support for Plex collection artwork (PKC settings toggle under Artwork )
- Fixes #408
2018-07-05 12:46:40 +02:00
Croneter
70ffc7b826 Fix Typo 2018-07-04 17:21:04 +02:00
Croneter
df7cbed268 Update readme 2018-07-04 16:27:51 +02:00
Croneter
94e3edb703 Extend Kodi metadata 2018-07-04 16:26:27 +02:00
Croneter
7e0439b146 Update translations 2018-07-04 14:48:30 +02:00
Croneter
ea311aba1a Merge branch 'beta-version' 2018-07-04 14:30:36 +02:00
Croneter
25c6d7c0d0 Update readme 2018-07-04 14:28:57 +02:00
Croneter
92f3a9fd63 Merge branch 'beta-version' 2018-07-04 09:13:13 +02:00
Croneter
8777a7b0af Beta version bump 2.2.7 2018-07-04 09:11:38 +02:00
Croneter
4f461ed02b Fix playback sometimes not starting up
- Fixes #492 UnboundLocalError
2018-07-04 09:08:39 +02:00
Croneter
768de346b1 Don't show artwork sync progress, reduce setting-writes
- Hopefully fixes #499
2018-07-04 08:17:37 +02:00
Croneter
f169e3da42 Use __future__ for contextmenu.py 2018-07-04 08:09:30 +02:00
Croneter
50b457cb15 Fix imports
- Fixes #503
2018-07-04 08:05:24 +02:00
Croneter
98ba2b8caa Ignore case when deciding whether to sync playlists 2018-07-04 07:46:30 +02:00
Croneter
1eed16dd52 Allow to only sync specific Plex or Kodi playlists 2018-07-02 20:23:16 +02:00
croneter
ef6aec132b Merge branch 'beta-version' 2018-06-28 20:15:06 +02:00
croneter
016208f18f Update readme 2018-06-28 20:14:50 +02:00
Croneter
fbe027e9e3 Merge branch 'beta-version' 2018-06-28 15:42:23 +02:00
Croneter
ee24df155a Beta version bump 2.2.6 2018-06-28 15:42:07 +02:00
Croneter
d918a26a00 Reset PKC setting to English only 2018-06-28 15:40:35 +02:00
Croneter
1cd037d4b6 Fix default settings string, only show in English
- Might fix #499
2018-06-28 15:38:33 +02:00
croneter
d0999aec42 Merge branch 'beta-version' 2018-06-24 16:26:37 +02:00
croneter
df764bce75 Beta version bump 2.2.5 2018-06-24 16:25:55 +02:00
croneter
aacd882e8b Fix AttributeError and add_update has crashed
- Fixes #500
2018-06-24 16:05:04 +02:00
croneter
832b1862e3 Merge branch 'beta-version' 2018-06-24 11:22:01 +02:00
croneter
75d2557da8 Beta version bump 2.2.4 2018-06-24 11:21:37 +02:00
croneter
108944c4b2 Fix LibrarySync crashing due to Plex Companion messages 2018-06-24 11:19:42 +02:00
croneter
b646f89970 Merge branch 'beta-version' 2018-06-24 10:52:03 +02:00
croneter
66a8b1d723 Beta version bump 2.2.3 2018-06-24 10:51:50 +02:00
croneter
832863ad71 Append tv show and SxxExx to episode playlist entries 2018-06-24 10:47:35 +02:00
croneter
a89a935ede Merge branch 'stable-version' into beta-version 2018-06-24 10:06:36 +02:00
croneter
25c777bcad Compatibility with Kodi Krypton Alpha 2 2018-06-24 10:04:30 +02:00
croneter
7ee68937bc Merge branch 'beta-version' 2018-06-23 19:22:12 +02:00
croneter
981e7a18e5 Version bump 2018-06-23 19:21:57 +02:00
croneter
463514186d Fix decoding of sys.argv 2018-06-23 19:15:24 +02:00
croneter
79281853ba Less logging 2018-06-23 18:44:31 +02:00
croneter
e8a35e06a9 Try to rename again 2018-06-23 18:35:06 +02:00
croneter
137425dcb1 GitHub fuckup: cases are ignored for Windows 2018-06-23 18:34:42 +02:00
Croneter
1234f61fc0 Fix encoding of file and path operations 2018-06-23 18:25:18 +02:00
Croneter
074c439e99 PEP8 2018-06-22 13:40:13 +02:00
Croneter
c03abddc27 Encode file paths correctly for all platforms 2018-06-22 13:39:38 +02:00
Croneter
9b76795ea4 Attempt to fix locking mechanisms
- Wraper to lock entire function was NOT working
2018-06-21 20:43:39 +02:00
Croneter
c440dc7779 Switch to absolute imports 2018-06-21 19:24:37 +02:00
Croneter
657ba47714 Switch to __future__ unicode_literals
- Primarily fixes os.walk only working with string paths, not unicode
2018-06-20 12:46:24 +02:00
Croneter
9c6fa31fda Fix UnboundLocalError for playlists 2018-06-20 12:31:18 +02:00
Croneter
2389f61b91 Speed up subtitle download to Kodi 2018-06-20 12:30:05 +02:00
croneter
ba04d85310 Merge branch 'stable-version' into beta-version 2018-06-17 17:09:07 +02:00
croneter
1136178381 Check all Kodi database versions before starting PKC 2018-06-17 13:38:58 +02:00
croneter
7297267613 Less logging 2018-06-17 13:21:22 +02:00
croneter
82283dbf44 Fix logging 2018-06-17 12:48:14 +02:00
croneter
de626f5cd9 Fix KeyError on non-PKC playback startup 2018-06-17 12:35:09 +02:00
Croneter
1fe962b1d5 Merge branch 'stable-version' 2018-06-15 15:17:18 +02:00
Croneter
0220c84554 PEP8 2018-06-15 15:16:55 +02:00
Croneter
9963163f0e PEP8 2018-06-15 15:15:35 +02:00
Croneter
12f1486f53 Merge branch 'stable-version' into beta-version 2018-06-15 14:41:15 +02:00
Croneter
0d77102117 Merge branch 'stable-version' 2018-06-15 14:40:54 +02:00
Croneter
51444111d2 PEP8 2018-06-15 14:40:29 +02:00
Croneter
1a58967111 PEP8 2018-06-15 14:15:39 +02:00
Croneter
b3c0374477 PEP8 2018-06-15 14:13:46 +02:00
Croneter
2c2029437a PEP8 2018-06-15 14:11:17 +02:00
Croneter
391cbab0be Remove obsolete variable 2018-06-15 14:09:40 +02:00
Croneter
0653b79306 PEP8 2018-06-15 14:08:43 +02:00
Croneter
c580638fb5 PEP8 2018-06-15 14:07:53 +02:00
Croneter
210701c366 PEP8 2018-06-15 14:07:08 +02:00
Croneter
bf8f238af4 PEP8 2018-06-15 14:05:48 +02:00
Croneter
18c6d8f784 Remove obsolete argument 2018-06-15 14:01:09 +02:00
Croneter
2037ba432b PEP8 2018-06-15 14:00:12 +02:00
Croneter
96dff29afe PEP8 2018-06-15 13:58:39 +02:00
Croneter
28ccd1a3b3 PEP8 2018-06-15 13:57:33 +02:00
Croneter
c14b9b4ed8 PEP8 2018-06-15 13:55:32 +02:00
Croneter
f427210f14 Remove obsolete function 2018-06-15 13:51:13 +02:00
Croneter
de846e7241 PEP8 2018-06-15 13:50:04 +02:00
Croneter
73403a8a7b PEP8 2018-06-15 13:49:18 +02:00
Croneter
1d277c26e1 PEP8 2018-06-15 13:47:22 +02:00
Croneter
d4bd51379d PEP8 2018-06-15 13:44:46 +02:00
Croneter
0d1275a1bb PEP8 2018-06-15 13:40:25 +02:00
Croneter
67d2638bc1 Update translations 2018-06-15 10:26:03 +02:00
Croneter
c2f0a193b5 Beta version bump 2018-06-15 10:22:20 +02:00
Croneter
3419e8869f Fix KeyError 2018-06-14 21:08:49 +02:00
Croneter
c03b7c52c4 Detect playback from playlist more reliable for add-on paths 2018-06-14 21:01:54 +02:00
Croneter
a36307e0aa Fix add-on paths playstate and Plex Companion for playlists 2018-06-14 19:54:35 +02:00
Croneter
0a55e7fee8 Rewire detection of add-on paths playlists playback
- Pain to fix Kodi bugs
2018-06-14 19:43:21 +02:00
Croneter
e09cfa8cb3 Clear kodi_playlist_playback attribute correctly 2018-06-14 18:57:49 +02:00
Croneter
b135980981 Fix Kodi telling Plex companion false playqueue position 2018-06-14 16:40:26 +02:00
Croneter
7ccfb61a7c Detect playback from a Kodi playlist 2018-06-14 16:27:13 +02:00
Croneter
108c88114b Fix playback from playlists for add-on paths 2018-06-14 15:54:12 +02:00
Croneter
01d269f995 Don't try to get a Kodi library items for Plex clips 2018-06-14 15:44:53 +02:00
Croneter
2152c789c7 Fix library sync crash due to PMS sending string, not unicode 2018-06-12 19:43:09 +02:00
Croneter
0dc1d70c1d Version bump 2018-06-12 08:16:45 +02:00
Croneter
f1e1d3772e Merge branch 'playlists' into beta-version 2018-06-12 08:09:16 +02:00
croneter
df2b0bf345 Update PKC stable version in readme 2018-06-09 14:42:11 +02:00
croneter
25192e695d Update readme 2018-06-09 14:39:36 +02:00
croneter
77378c2fd9 Make 2.1.1 available for stable 2018-06-09 14:33:51 +02:00
croneter
ece0223d31 Version bump 2018-06-09 13:14:04 +02:00
croneter
0c41bb1604 Use unicode paths, not strings for dir_util.copy_tree 2018-06-09 13:13:56 +02:00
Croneter
45c4d7f479 Fix Library Sync crash for Android
- Caused by PKC wanting to copy permissions bits as well
2018-06-09 13:13:44 +02:00
croneter
08a863f707 Version bump 2018-06-09 13:11:46 +02:00
croneter
602966e035 Use unicode paths, not strings for dir_util.copy_tree 2018-06-09 13:09:31 +02:00
Croneter
15f3313c1f Fix Library Sync crash for Android
- Caused by PKC wanting to copy permissions bits as well
2018-06-09 12:56:21 +02:00
Croneter
69b8e98b39 Merge branch 'master' into playlists 2018-06-08 10:24:09 +02:00
Croneter
0f16ebe78f Version bump 2018-06-08 10:17:41 +02:00
Croneter
1f144d693c Fix migration not working 2018-06-08 10:07:47 +02:00
Croneter
c5ca8910d7 Merge branch 'master' into playlists 2018-06-07 17:41:46 +02:00
Croneter
ffb0496e13 Version bump 2018-06-07 17:25:25 +02:00
Croneter
807fb1614e Fix context menu missing "Delete item from PMS"
- Fixes #483
2018-06-07 17:23:24 +02:00
Croneter
ee1a33cc4c Fix searching for PMS if there is no internet connection
- Fixes #485
2018-06-07 17:15:37 +02:00
Croneter
65561eea47 Fix DB reset on Startup if PMS connection fails
- Should fix #484
2018-06-07 17:11:13 +02:00
Croneter
2f90a29acf Fix resume for On Deck widget for direct paths 2018-06-07 16:27:41 +02:00
croneter
136461160f Merge branch 'master' into playlists 2018-06-03 14:26:55 +02:00
croneter
cfecee44bf Version bump 2018-06-03 14:11:14 +02:00
croneter
9b1085c134 Less logging
- Fixes #482
2018-06-03 14:04:11 +02:00
croneter
0486934d81 Force a sync on startup even if Kodi is playing something
- Fixes #482
2018-06-03 13:48:00 +02:00
croneter
f9121d281c Direct paths: Don't download PMS sections twice 2018-06-03 13:32:25 +02:00
croneter
87b22f1588 Revert "Make sure that LOCK is released after adding one element"
This reverts commit c05b772e90.
- Should fix a racing condition if the playlist is cleared (picked up by both kodimonitor and playqueue monitor)
2018-06-02 17:31:57 +02:00
croneter
48810a227f Revert "Fix playqueue monitoring locking mechanism"
This reverts commit 1e43f1cc77.
- Should fix a racing condition if the playlist is cleared (picked up by both kodimonitor and playqueue monitor)
2018-06-02 17:26:36 +02:00
croneter
147d35ca24 Include Plex Home username in "Log-out Plex Home user" 2018-06-02 16:38:10 +02:00
croneter
bd73c03078 Merge branch 'hotfixes' into playlists 2018-06-02 16:12:47 +02:00
croneter
658762dbab Version bump 2018-06-01 20:47:19 +02:00
croneter
c10d4381c8 Update Czech translation 2018-06-01 20:44:21 +02:00
croneter
e85e514c8b Fix endless reboots if Plex music library missing 2018-06-01 20:36:24 +02:00
croneter
faf17f34d1 Merge branch 'hotfixes' of https://github.com/croneter/PlexKodiConnect into hotfixes 2018-06-01 19:56:47 +02:00
Croneter
ff4217b488 Specify exception type 2018-06-01 18:49:43 +02:00
Croneter
a4273c6c6e Declare PMS connection dead on first failed connection 2018-06-01 18:48:45 +02:00
Croneter
a6881a8a32 Fix KeyErrors if Kodi player does not return position
- Partially fixes #481
2018-06-01 18:43:56 +02:00
Croneter
7bf6d19708 Fix AttributeError 2018-05-30 13:10:54 +02:00
Croneter
114895c183 Fix logging 2018-05-30 11:24:51 +02:00
Croneter
b729cf5423 Fix logging 2018-05-30 10:41:46 +02:00
Croneter
7c92c01047 Fix Typo 2018-05-30 08:29:33 +02:00
Croneter
1f0977ec77 Fix Plex Companion failing leading to PMS connection loss 2018-05-30 08:20:30 +02:00
Croneter
06f9f6a7a5 Fix PKC add-on setting user changes not saving 2018-05-30 07:53:30 +02:00
croneter
8a2622c3f8 Fix playback of last item not starting up 2018-05-27 11:36:54 +02:00
croneter
ecc67b5707 Use float instead of int for resume 2018-05-27 11:13:19 +02:00
croneter
60bfff16dd Remove obsolete setting of resumetime for library items 2018-05-27 10:57:04 +02:00
croneter
6a66acb44c Use float for resume and runtime instead of int 2018-05-27 10:52:04 +02:00
croneter
7b4a3da023 Version bump 2018-05-26 19:00:19 +02:00
croneter
5c33f3c02a Fix episode widget resume not working (add-on paths)
- Hack alert, really...
- Need to reset the Kodi DB
2018-05-26 18:54:38 +02:00
croneter
ac7b7bb96d Revert "Another attempt to fix episode resume from widgets"
This reverts commit 04044ac896.
2018-05-26 17:28:17 +02:00
croneter
04044ac896 Another attempt to fix episode resume from widgets
- Tried to get as close as possible to emby DB
2018-05-26 17:28:11 +02:00
croneter
f68b167c0d Revert "Attempt to fix resume from widget not working"
This reverts commit a6bc0b9e69.
2018-05-24 19:36:14 +02:00
croneter
a6bc0b9e69 Attempt to fix resume from widget not working 2018-05-24 19:28:50 +02:00
croneter
8fbca537f4 Attach missing filename to plugin call 2018-05-22 20:31:32 +02:00
croneter
d1390c25c7 Revert "Try to fix widget resume for episodes for addon paths"
This reverts commit 95354fe564.
2018-05-20 18:54:31 +02:00
croneter
95354fe564 Try to fix widget resume for episodes for addon paths
- By introducing a second file_id in file and bookmark table
2018-05-20 18:15:09 +02:00
croneter
ca1151bc6d Speed up PKC start-up 2018-05-20 14:28:56 +02:00
croneter
ed213a4b34 Fix ValueError if plex.tv returns Plex Cloud URIs 2018-05-20 14:23:21 +02:00
croneter
f00b5fe59d Revert "Fix ValueError if plex.tv returns Plex Cloud URIs"
This reverts commit a7702573ad.
2018-05-20 14:22:05 +02:00
croneter
fe3293e986 Speed up checking of PMS connection, e.g. on startup 2018-05-20 14:17:49 +02:00
croneter
a7702573ad Fix ValueError if plex.tv returns Plex Cloud URIs 2018-05-20 14:16:50 +02:00
croneter
45e6baa34d Revert "Default to not show image caching notifications"
This reverts commit 5a993a2bf0.
2018-05-20 13:55:23 +02:00
croneter
80acc668ec Improve collection lookup; fix PKC caching wrong url 2018-05-20 13:52:23 +02:00
croneter
75994bff47 Prettify 2018-05-20 12:34:44 +02:00
croneter
7b7ee9fa8d PEP8 2018-05-20 12:28:49 +02:00
croneter
978278db7b Remove obsolete kwarg 2018-05-20 12:26:52 +02:00
croneter
a65981c1de Update translations 2018-05-18 19:54:36 +02:00
Croneter
ad9de09027 Merge branch 'hotfixes' into playlists 2018-05-04 19:03:47 +02:00
Croneter
569cb71ca8 Refactor playlist and playqueue classes 2018-05-03 17:30:29 +02:00
Croneter
5bfe9b7c7f Also do a playlist full sync on library full sync 2018-05-03 17:20:23 +02:00
Croneter
25a9a3c4ce Optimize logging 2018-05-03 17:13:00 +02:00
Croneter
73655d354f Optimize logging 2018-05-03 14:02:00 +02:00
Croneter
2a862b5169 Optimize length for playlist filename (m3u instead of m3u8) 2018-05-03 08:29:14 +02:00
Croneter
6bcddc8382 raise PlaylistError instead of just returning 2018-05-03 08:24:41 +02:00
Croneter
ca1033801d Add setting to (de)activate playlist sync 2018-05-03 08:20:55 +02:00
Croneter
c61fc3241f Ensure that music playlists are not synced if music is deactivated 2018-05-03 08:01:26 +02:00
Croneter
df7bfd2e0c Improve logging 2018-05-03 07:56:32 +02:00
Croneter
f5a457a87d Rename variable 2018-05-03 07:53:28 +02:00
Croneter
a8d02bffdc Merge branch 'master' into playlists 2018-05-03 07:43:45 +02:00
Croneter
0c3db3e2f8 Delete all synced playlists on PKC (database) reset 2018-05-02 19:13:56 +02:00
Croneter
5b4ed1d6a6 Fix music playlists 2018-05-02 18:50:31 +02:00
Croneter
b00ec8989c Optimize logging 2018-05-02 18:22:23 +02:00
Croneter
63c829b042 Fix error message upon playlist deletion 2018-05-02 18:14:33 +02:00
Croneter
81b1d18192 Don't let librarysync crash if Plex playlist deletion doesnt work 2018-05-02 18:05:27 +02:00
Croneter
3016c9747f Fix possible AttributeError 2018-05-02 17:59:06 +02:00
Croneter
c3b649c1b1 Clean up code 2018-05-02 17:55:27 +02:00
Croneter
799f9ba25a Fix Plex playlist duplicating 2018-05-02 17:55:19 +02:00
Croneter
c98a8456ff Introduce playlist locking; override watchdog dispatching 2018-05-02 17:45:31 +02:00
Croneter
2971dd3f7c Extend websocket listening to Plex playlist changes 2018-05-02 17:27:35 +02:00
Croneter
0baa081dc6 Optimize logging 2018-05-02 16:57:27 +02:00
Croneter
5fd6587ff7 Fix deleting Plex playlists 2018-05-02 16:47:03 +02:00
Croneter
2f25453fe2 Plex playlist creation does not update hash; fix Kodi monitoring 2018-05-02 16:46:54 +02:00
Croneter
02e1917072 Increase logging 2018-05-02 16:20:20 +02:00
Croneter
94641b9ed6 Improve logging 2018-05-02 15:59:59 +02:00
Croneter
c4841ed946 Fix logging 2018-05-02 15:56:31 +02:00
Croneter
c36746dbdf Fix use of wrong function 2018-05-02 15:50:26 +02:00
Croneter
b33ed4ccbe Fix putting more items on Plex playlist 2018-05-02 15:44:54 +02:00
Croneter
61b0645314 Rename function 2018-05-02 15:34:21 +02:00
Croneter
e637f36a21 Save Plex playlist details on initialization 2018-05-02 15:17:58 +02:00
Croneter
376338a9b0 Fix initializing of playlists on the Plex side 2018-05-02 09:33:37 +02:00
Croneter
ba0aff0f54 Rename function 2018-05-01 18:08:31 +02:00
Croneter
6c3c9a4f93 Use m3u instead of m3u8 2018-05-01 17:59:51 +02:00
Croneter
9f4dbe6ee5 Increase logging 2018-05-01 17:13:00 +02:00
Croneter
341571e232 Fix saving Plex updatedAt value for new playlists 2018-05-01 17:09:58 +02:00
Croneter
8ee20b1bba Fix sync if Kodi playlist has been deleted 2018-05-01 16:52:12 +02:00
Croneter
20fef65b3d Fix playlist id and naming on playlist creation 2018-05-01 16:48:31 +02:00
Croneter
7ddfc0143d Revert "Remove API method playlist_type()"
This reverts commit 7b64b794f2.
2018-05-01 16:44:40 +02:00
Croneter
7dde71734c Fix updating playlist object from XML 2018-05-01 16:41:10 +02:00
Croneter
1a3a94894b Fix AttributeError for exceptions 2018-05-01 16:37:27 +02:00
Croneter
ff72648570 Fix AttributeError 2018-05-01 16:35:26 +02:00
Croneter
f8682fb8cb Fix AttributeErrors 2018-05-01 16:33:49 +02:00
Croneter
e2c90ac0f4 Fix AttributeErrors for Plex playlist id 2018-05-01 16:31:03 +02:00
Croneter
7b64b794f2 Remove API method playlist_type() 2018-05-01 16:30:04 +02:00
Croneter
6f6fb16352 Hopefully fix download of Plex playlists 2018-05-01 16:27:18 +02:00
Croneter
b448d1c06f Fix setting Kodi playlist path 2018-05-01 16:20:57 +02:00
Croneter
e8aba6b77b Increase logging 2018-05-01 16:18:06 +02:00
Croneter
26c588828e Fix ValueError 2018-05-01 16:12:25 +02:00
Croneter
4e16756829 Fix AttributeErrors 2018-05-01 16:09:35 +02:00
Croneter
1780b3948b Fix KeyError 2018-05-01 16:08:24 +02:00
Croneter
0cedfd7a7d Fix Plex playlist download 2018-05-01 16:04:26 +02:00
Croneter
df2b3810bf Fix FutureWarning for etree xmls 2018-05-01 15:24:58 +02:00
Croneter
1de2390cdd Fix IndexError for setting playlist path 2018-05-01 15:23:39 +02:00
Croneter
892458981d Fix AttributeError for setting playlist path 2018-05-01 15:21:29 +02:00
Croneter
151c679e29 Fix WindowsError for WatchDog trying to monitor playlists 2018-05-01 15:13:42 +02:00
Croneter
5ce97246d1 Fix TypeError for getting a PMS playlist 2018-05-01 14:59:57 +02:00
Croneter
f622eab809 Fix TypeError for PlaylistObject instantiation 2018-05-01 14:56:07 +02:00
Croneter
b50506b891 Fix AttributError due to false import 2018-05-01 14:55:11 +02:00
Croneter
e38f99f088 Playlist sync support, part 2 2018-05-01 14:48:49 +02:00
Croneter
ac8b8e6153 Optimize start of playlist monitor 2018-04-30 15:01:43 +02:00
Croneter
b7644198c7 Merge branch 'master' into playlists 2018-04-29 15:12:11 +02:00
Croneter
29d9a29cb7 Merge branch 'hotfixes' into playlists 2018-04-29 14:44:32 +02:00
Croneter
0a83d6c084 Playlist sync support, part 1 2018-04-28 09:12:29 +02:00
517 changed files with 87964 additions and 17137 deletions

5
.codacy.yaml Normal file
View file

@ -0,0 +1,5 @@
exclude_paths:
- 'resources/lib/watchdog/**'
- 'resources/lib/pathtools/**'
- 'resources/lib/pathtools/**'
- 'resources/lib/defused_etree.py'

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
ko_fi: A8182EB

41
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,41 @@
---
name: Bug report
about: Create a report to help us improve. Please read the instructions carefully.
title: ''
labels: ''
assignees: ''
---
## Help yourself
* I did try to restart Kodi :-)
* I checked the [PKC Frequently Asked Questions on the PKC wiki](https://github.com/croneter/PlexKodiConnect/wiki/faq)
* I did try to reset the Kodi database by going to `PKC Settings -> Advanced -> "Reset the database and optionally reset PlexKodiConnect"` and then hitting YES, NO
* I did check the [existing issues on Github](https://github.com/croneter/PlexKodiConnect/issues)
## Describe the bug
A clear and concise description of what the bug is.
## To Reproduce
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
## Expected behavior
A clear and concise description of what you expected to happen.
## You need to attach a KODI LOG FILE!
A Kodi debug log file is needed that you recorded while you reproduced the bug. **Do clean your log of all Plex tokens (="Plex passwords")!!!** .
1. Activate Kodi's debug logging by going to the Kodi `Settings` -> `System` -> `Logging`. Then toggle the `Enable debug logging` setting.
2. Restart Kodi to start with a "fresh" log file.
3. Reproduce the bug.
4. Follow the [Kodi instructions](http://kodi.wiki/view/Log_file/Easy) to grab/share the Kodi log file. Usually only `kodi.log` is needed
* You can [find the log file here](http://kodi.wiki/view/Log_file/Advanced#Location)
5. **Delete all references to any of your Plex tokens** by searching for `X-Plex-Token` and `accesstoken` and replacing the strings just after that!
* It's easiest if you copy your token, then use Search&Replace for the entire log file
* You don't want others to have access to your Plex installation....
6. Drop your log file here in this issue. Or use a free pasting-service like https://pastebin.com and include the link to it here
I am aware that I can delete Plex tokens that I accidentially posted by following the [instructions on the PKC wiki](https://github.com/croneter/PlexKodiConnect/wiki/How-to-Report-A-Bug#i-published-my-plex-token-to-some-forum-or-github-anyone-can-now-access-my-plex-server)

View file

@ -1,9 +1,12 @@
[![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.26-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
[![Kodi Leia stable version](https://img.shields.io/badge/Kodi_Leia_STABLE-latest-blue.svg?maxAge=60&style=flat) ](https://croneter.github.io/pkc-source/repository.plexkodiconnect.Kodi-Leia.STABLE.zip)
[![Kodi Leia beta version](https://img.shields.io/badge/Kodi_Leia_BETA-latest-red.svg?maxAge=60&style=flat) ](https://croneter.github.io/pkc-source/repository.plexkodiconnect.Kodi-Leia.BETA.zip)
[![Kodi Matrix stable version](https://img.shields.io/badge/Kodi_Matrix_STABLE-latest-blue.svg?maxAge=60&style=flat) ](https://croneter.github.io/pkc-source/repository.plexkodiconnect.Kodi-Matrix.STABLE.zip)
[![Kodi Matrix beta version](https://img.shields.io/badge/Kodi_Matrix_BETA-latest-red.svg?maxAge=60&style=flat) ](https://croneter.github.io/pkc-source/repository.plexkodiconnect.Kodi-Matrix.BETA.zip)
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)
[![Forum](https://img.shields.io/badge/forum-plex-orange.svg?maxAge=60&style=flat)](https://forums.plex.tv/discussion/210023/plexkodiconnect-let-kodi-talk-to-your-plex)
[![Donate](https://img.shields.io/badge/donate-kofi-blue.svg)](https://ko-fi.com/A8182EB)
[![GitHub issues](https://img.shields.io/github/issues/croneter/PlexKodiConnect.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/issues) [![GitHub pull requests](https://img.shields.io/github/issues-pr/croneter/PlexKodiConnect.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/pulls) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/a66870f19ced4fb98f94d9fd56e34e87)](https://www.codacy.com/app/croneter/PlexKodiConnect?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=croneter/PlexKodiConnect&amp;utm_campaign=Badge_Grade)
@ -11,7 +14,12 @@
# PlexKodiConnect (PKC)
**Combine the best frontend media player Kodi with the best multimedia backend server Plex**
PKC combines the best of Kodi - ultra smooth navigation, beautiful and highly customizable user interfaces and playback of any file under the sun - and the Plex Media Server.
PKC synchronizes your media from your Plex server to the native Kodi database. Hence:
- Use virtually any other Kodi add-on
- Use any Kodi skin, completely customize Kodi's look
- Browse your media very fluently (cached artwork)
- Automatically get additional artwork (more than Plex offers)
- Use Plex features with a Kodi interface
Have a look at [some screenshots](https://github.com/croneter/PlexKodiConnect/wiki/Some-PKC-Screenshots) to see what's possible.
@ -21,7 +29,6 @@ Unfortunately, the PKC Kodi repository had to move because it stopped working (t
### Content
* [**Download and Installation**](#download-and-installation)
* [**What does PKC do?**](#what-does-pkc-do)
* [**Warning**](#warning)
* [**PKC Features**](#pkc-features)
* [**Additional Artwork**](#additional-artwork)
@ -32,19 +39,7 @@ Unfortunately, the PKC Kodi repository had to move because it stopped working (t
### 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) |
### What does PKC do?
PKC synchronizes your media from your Plex server to the native Kodi database. Hence:
- Use virtually any other Kodi add-on
- Use any Kodi skin, completely customize Kodi's look
- Browse your media at full speed (cached artwork)
- Automatically get additional artwork (more than Plex offers)
- Enjoy Plex features using the Kodi interface
Using the Kodi file manager, add [https://croneter.github.io/pkc-source](https://croneter.github.io/pkc-source) as a new Kodi `Web server directory (HTTPS)` source, then install the PlexKodiConnect repository from this new source "from ZIP file". See the [github wiki installation manual](https://github.com/croneter/PlexKodiConnect/wiki/Installation) for a detailed guide. Kodi will update PKC automatically.
### 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 ;-)).
@ -53,17 +48,20 @@ Some people argue that PKC is 'hacky' because of the way it directly accesses th
### PKC Features
- Support of Kodi 18 Leia (and Kodi 17 Krypton)
- Support for Kodi 18 Leia and Kodi 19 Matrix
- Preliminary support for Kodi 20 Nexus. Keep in mind that development for Kodi Nexus has not even officially reached alpha stage - any issues you encounter are probably caused by that
- [Skip intros](https://support.plex.tv/articles/skip-content/)
- [Amazon Alexa voice recognition](https://www.plex.tv/apps/streaming-devices/amazon-alexa)
- [Cinema Trailers & Extras](https://support.plex.tv/articles/202934883-cinema-trailers-extras/)
- [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
- Automatically sync Plex playlists to Kodi playlists and vice-versa
- [Plex Transcoding](https://support.plex.tv/hc/en-us/articles/200250377-Transcoding-Media)
- Automatically download more artwork from [Fanart.tv](https://fanart.tv/), just like the Kodi addon [Artwork Downloader](http://kodi.wiki/view/Add-on:Artwork_Downloader)
- Automatically group movies into [movie sets](http://kodi.wiki/view/movie_sets)
- [Direct play](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Play) from network paths (e.g. "\\\\server\\Plex\\movie.mkv"), something unique to PKC
- Delete PMS items from the Kodi context menu
- PKC is available in the following languages:
- PKC is available in the following languages. [Please help and easily translate PKC!](https://www.transifex.com/croneter/pkc)
+ English
+ German
+ Czech, thanks @Pavuucek
@ -78,7 +76,9 @@ Some people argue that PKC is 'hacky' because of the way it directly accesses th
+ Portuguese, thanks @goncalo532
+ Russian, thanks @UncleStark
+ Hungarian, thanks @savage93
+ [You can easily help to translate PKC!](https://www.transifex.com/croneter/pkc)
+ Ukrainian, thanks @uniss
+ Lithuanian, thanks @egidusm
+ Korean, thanks @so-o-bima
### Additional Artwork
PKC uses additional artwork for free from [TheMovieDB](https://www.themoviedb.org). Many thanks for lettings us use the API, guys!
@ -89,13 +89,11 @@ I'm not in any way affiliated with Plex. Thank you very much for a small donatio
**Full disclaimer:** I will see your name and address if you use PayPal. Rest assured that I will not share this with anyone.
[![Donations](https://az743702.vo.msecnd.net/cdn/kofi1.png?v=a)](https://ko-fi.com/A8182EB)
![ETH-Donations](https://chart.googleapis.com/chart?chs=150x150&cht=qr&chld=L0&chl=0x0f57D98E08e617292D8bC0B3448dd79BF4Cf8e7F)
**Ethereum address:
**Ethereum address for donations:
0x0f57D98E08e617292D8bC0B3448dd79BF4Cf8e7F**
![BTX-Donations](https://chart.googleapis.com/chart?chs=150x150&cht=qr&chld=L0&chl=3BhwvUsqAGtAZodGUx4mTP7pTECjf1AejT)
**Bitcoin address:
**Bitcoin address for donations:
3BhwvUsqAGtAZodGUx4mTP7pTECjf1AejT**

577
addon.xml
View file

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.0.26" provider-name="croneter">
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.15.0" provider-name="croneter">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.9.1" />
<import addon="plugin.video.plexkodiconnect.movies" version="2.0.4" />
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.4" />
<import addon="script.module.defusedxml" version="0.5.0"/>
<import addon="script.module.six" />
<import addon="plugin.video.plexkodiconnect.movies" version="2.1.3" />
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.1.3" />
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video audio image</provides>
@ -19,6 +21,9 @@
</item>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Native Integration of Plex into Kodi</summary>
<description lang="en">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">Use at your own risk</disclaimer>
<platform>all</platform>
<license>GNU GENERAL PUBLIC LICENSE. Version 2, June 1991</license>
<forum>https://forums.plex.tv</forum>
@ -58,6 +63,9 @@
<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>
<disclaimer lang="da_DK">Brug på eget ansvar</disclaimer>
<summary lang="it_IT">Integrazione nativa di Plex su Kodi</summary>
<description lang="it_IT">Connetti Kodi al tuo Plex Media Server. Questo plugin assume che tu gestisca tutti i video con Plex (e non con Kodi). Potresti perdere i dati dei film e della musica già memorizzati nel database di Kodi (questo plugin modifica direttamente il database stesso). Usa a tuo rischio e pericolo!</description>
<disclaimer lang="it_IT">Usa a tuo rischio e pericolo</disclaimer>
<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>
@ -67,435 +75,206 @@
<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.26 (beta only):
- Reduce CPU strain for artwork caching progress
- Fallback connection if plex.direct does not resolve
- Prettify Plex context menu, thanks @dazedcrazy
- Default to not show image caching notifications
<summary lang="uk_UA">Нативна інтеграція Plex в Kodi</summary>
<description lang="uk_UA">Підключає Kodi до серверу Plex. Цей плагін передбачає, що ви керуєте всіма своїми відео за допомогою Plex (і ніяк не Kodi). Ви можете втратити дані, які вже зберігаються у відео та музичних БД Kodi (оскільки цей плагін безпосередньо їх змінює). Використовуйте на свій страх і ризик!</description>
<disclaimer lang="uk_UA">Використовуйте на свій ризик</disclaimer>
<disclaimer lang="lv_LV">Lieto uz savu atbildību</disclaimer>
<summary lang="sv_SE">Inbyggd integrering av Plex i Kodi</summary>
<description lang="sv_SE">Anslut Kodi till din Plex Media Server. Detta tillägg antar att du hanterar alla dina filmer med Plex (och ingen med Kodi). Du kan förlora data redan sparad i Kodis video och musik databaser (eftersom detta tillägg direkt ändrar dem). Använd på egen risk!</description>
<disclaimer lang="sv_SE">Använd på egen risk</disclaimer>
<summary lang="lt_LT">Natūralioji „Plex“ integracija į „Kodi“</summary>
<description lang="lt_LT">Prijunkite „Kodi“ prie „Plex Medija Serverio“. Šiame papildinyje daroma prielaida, kad valdote visus savo vaizdo įrašus naudodami „Plex“ (ir nė vieno su „Kodi“). Galite prarasti jau saugomus „Kodi“ vaizdo įrašų ir muzikos duomenų bazių duomenis (kadangi šis papildinys juos tiesiogiai pakeičia). Naudokite savo pačių rizika!</description>
<disclaimer lang="lt_LT">Naudokite savo pačių rizika</disclaimer>
<summary lang="ko_KR">Plex를 Kodi에 기본 통합</summary>
<description lang="ko_KR">Kodi를 Plex Media Server에 연결합니다. 이 플러그인은 Plex로 모든 비디오를 관리하고 Kodi로는 관리하지 않는다고 가정합니다. Kodi 비디오 및 음악 데이터베이스에 이미 저장된 데이터가 손실 될 수 있습니다 (이 플러그인이 직접 변경하므로). 자신의 책임하에 사용하십시오!</description>
<disclaimer lang="ko_KR">자신의 책임하에 사용</disclaimer>
<news>version 2.15.0:
- versions 2.14.3-2.14.4 for everyone
- Direct Paths: Fix TypeError: "element indices must be integers" on playback startup [backport]
- Refactor stream code and fix Kodi not activating subtitle when it should [backport]
- Add playback settings to let the user choose whether Plex or Kodi provides the default audio or subtitle stream on playback start [backport]
- Update translations from Transifex [backport]
version 2.0.25 (beta only):
- Fix migration not working correctly for re-connecting PMS
- Fix PMS showing up twice
- Improve artwork caching counter in PKC settings
version 2.14.4 (beta only):
- Tell the PMS if a video's audio stream or potentially subtitle stream has changed. For subtitles, this functionality is broken due to a Kodi bug
- Transcoding: Fix Plex burning-in subtitles when it should not
- Fix logging if fanart.tv lookup fails: be less verbose
- Large refactoring of playlist and playqueue code
- Refactor usage of a media part's id
version 2.0.24 (beta only):
- WARNING: You will need to reset the Kodi database! Sorry for that...
- PKC will force you to re-connect with your PMS
- Use plex.direct url instead of local ip to use correct SSL certificate; thus fix artwork caching
- Revert "Increase timeout between syncing images"
- Don't ask user for DB reset if forced by PKC
- Ensure movies and tv shows are synced before music
- Ensure a later migration if user downgraded PKC
version 2.14.3 (beta only):
- Implement "Reset resume position" from the Kodi context menu
version 2.0.23 (beta only):
- WARNING: You will need to reset the Kodi database!
- Finally support for Extras!
- Fix context menu not working for shows in library view
- Fix Plex Companion music playstate status for iOS
- Show caching progress and FanartTV lookup progress in PKC settings
- Fix rare library sync errors
- Fix ValueError for third party add-ons calling PKC
- Tweak PKC settings
version 2.14.2:
- version 2.14.1 for everyone
version 2.0.22 (beta only):
- Fix Recently Added for tv shows not working
- Fix PKC crashing on startup
version 2.14.1 (beta only):
- Use Plex settings for audio and subtitle stream selection. This is a best guess regarding subtitles as Plex and Kodi are not sharing much info
- Fix PlexKodiConnect setting the Plex subtitle to None
- Download landscape artwork from fanart.tv, thanks @geropan
- Revert "Fix PlexKodiConnect changing subtitles for all videos on the PMS"
version 2.0.21 (beta only):
- Fix TV show artwork Kodi native library (reset Kodi DB!)
- Cache missing posters and backgrounds/fanart on Kodi startup
- Add toggle to deactivate image caching during playback
- Increase timeout between syncing images
- Fix music database if new music is added in the background
version 2.14.0:
- Fix PlexKodiConnect changing or removing subtitles for every video on the PMS
- version 2.13.1-2.13.2 for everyone
version 2.0.20 (beta only):
- Fix missing episode poster in certain views. You will have to manually reset your Kodi database to benefit
- Fix episode artwork sometimes not being complete
- Fix IndexError for certain Plex channels
- Kodi Leia: Fix playback failing
- Fix TV On Deck direct paths asking to choose between different media
version 2.13.2 (beta only):
- Fix a racing condition that could lead to the sync getting stuck
- Fix RecursionError: maximum recursion depth exceeded
- Websocket Fix AttributeError: 'NoneType' object has no attribute 'is_ssl'
version 2.0.19 (beta only):
- Fix PKC playback startup getting caught in infinity loop
- Rewire library sync, suspend sync during playback
- Fix playback failing in certain cases
- Fix PKC not working anymore after using context menu on songs
- Fix deletion of Plex music items
- Code cleanup
version 2.13.1 (beta only):
- Fix a racing condition that could lead to the sync process getting stuck
- Fix likelyhood of `database is locked` error occuring
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.13.0:
- Support for the Plex HAMA agent to let Kodi identify animes (using Kodi's uniqueID 'anidb')
- Support forced HAMA IDs when using tvdb uniqueID
- version 2.12.26 for everyone
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
version 2.0.15 (beta only):
- Fix Plex Companion thinking video is playing again
- Warn if "Play next video automatically" is enabled, cause it breaks PKC playback report
- Don't clean the Kodi file table
- Only remember which player has been active if we got a Plex id
- Hopefully fix ValueError for datetime.utcnow()
version 2.0.14 (beta only):
- Fix resetting PKC player state - should fix PKC telling the PMS that an old, just-played item is playing
- Play the selected element first, then add the Kodi playqueue to the Plex playqueue
- Ensure that playstate for ended (not stopped) video is recorded correctly
- Make sure that LOCK is released after adding one element
version 2.0.13 (beta only):
- Fix resume for On Deck and browse by folder
- Fix PKC sometimes telling wrong item being played
- Don't tell PMS last item is playing if non-Plex item is played
- Fix rare KeyError for playback including trailers
- Use an empty video file to "fail" playback
- Use identical add-on paths for On Deck and browsing folders
version 2.0.12 (beta only):
- Fix resume not working for some Kodi interface languages
- Fix widget navigating to entire TV show not working
- Fix library sync crash TypeError
- Revert "Revert "Fix for "In Progress" not appearing""
- Simplify error message
version 2.0.11 (beta only):
- WARNING: You will need to reset the Kodi database!
- Fix playback for add-on paths
- Fix artwork for episodes for add-on paths
- Revert "Fix for "In Progress" not appearing"
- Fix playback resuming potentially too often
version 2.0.10 (beta only):
- Fix wrong item being reported using direct paths
- Direct paths: correctly clean up after context menu play
- Always resume playback if playback initiated via context menu
- Do not play trailers for resumable movies using playback via PMS
- Fix for "In Progress" widget not appearing
- Fix correctly recording ended (not stopped!) video
- Don't record last played date if state unwatched
- Clean Kodi DB more thoroughly after playback start via PMS
version 2.0.9 (beta only):
- Fix AttributeError on playback start
version 2.0.8 (beta only):
- Fix videos not being correctly marked as played
- Improve playback startup resiliance
- Fix playerstates not being copied/reset correctly
- Fix tv shows not being correctly deleted
- Fix episode rating not being correct
- Make generally sure that we're correctly deleting videos from the Kodi DB
- Fix disabling of background sync (websockets)
version 2.0.7 (beta only):
- Fix another UnicodeDecodeError for playlists
- Hardcode plugin-calls instead of using urlencode
- Fix Kodi 18 log warnings by declaring all settings variables
version 2.0.6 (beta only):
- Addon paths playback was basically broken - hope it works again!
- Fixes to add-on paths playback startup
- Fix UnicodeDecodeError for playqueue logging
version 2.0.5 (beta only):
- WARNING: You will need to reset the Kodi database!
- Fix art and show info not showing for addon paths
- Fix episode information not working
- Big Kodi DB overhaul - ensure video metadata updates/deletes correctly
- Artwork code overhaul
- Greatly speed up switch of PMS
- And a lot of other stuff
version 2.0.4 (beta only):
- WARNING: You will need to reset the Kodi database!
- Many improvements to the Kodi database handling which should get rid of some weird bugs
- Many improvements to playback startup
- Fix info screen and actors not working
- Fix Companion displaying and selecting wrong subtitle
- Don't cache subtitles if direct playing
- Wipe all existing resume point, e.g. on user switch
- Don't mess with Kodi's screensaver settings
- Inhibit idle shutdown only during initial sync
- Fix KeyError for server discovery
- Increase Python requests dependency to version 2.9.1
- Re-introduce PlexKodiConnect dependency add-ons for movies and tv shows
- And a lot of other stuff
version 2.0.3 (beta only):
- Fix Alexa playback
- Fix Kodi boot loop
- Fix playback being reported to the wrong Plex user
- Fix GB/BBFC content ratings
- Fix KeyError when browsing On Deck
- Make sure that empty XML elements get deleted
- Code optimizations
version 2.0.2 (beta only):
- Fix playback reporting not starting up correctly
- Fix playback cleanup if PKC causes stop
- Always detect if user resumes playback
- Enable resume within a playqueue
- Compare playqueue items more reliably
version 2.0.1 (beta only):
- Fix empty On Deck for tv shows
- Fix trailers not playing
version 2.0.0 (beta only):
- HUGE code overhaul - Remember that you can go back to earlier version ;-)
- Completely rewritten Plex Companion
- Completely rewritten playback startup
- Tons of fixes, see the Github changelog for more details
- WARNING: You will need to reset the Kodi database!
version 1.8.18:
- Russian translation, thanks @UncleStark, @xom2000, @AlexFreit
- Fix Plex context menu not showing up
- Deal better with missing stream info (e.g. channels)
- Fix AttributeError if Plex key is missing
version 1.8.17:
- Hopefully fix stable repo
- Fix subtitles not working or showing up as Unknown
- Enable channels for Plex home users
- Remove obsolete PKC settings show contextmenu
version 1.8.16:
- Add premiere dates for movies, thanks @dazedcrazy
- Fix items not getting marked as fully watched
version 1.8.15:
- version 1.8.14 for everyone
version 2.12.26 (beta only):
- Add an additional Plex Hub "PKC Continue Watching" that merges the Plex Continue Watching with On Deck
- Fix auto-picking of video stream if several video versions are available
- Update translations
version 1.8.14 (beta only):
- Greatly speed up displaying context menu
- Fix IndexError e.g. for channels if stream info missing
- Sleep a bit before marking item as fully watched
- Don't sleep before updating playstate to fully watched (if you watch on another Plex client)
- Fix KeyError for TV live channels for getGeople
version 2.12.25:
- Update websocket client to 0.59.0. Fix threading issues and AttributeErrors
version 1.8.13 (beta only):
- Background sync now picks up more PMS changes
- Detect Plex item deletion more reliably
- Fix changed Plex metadata not synced repeatedly
- Detect (some, not all) changes to PKC settings and apply them on-the-fly
- Fix resuming interrupted sync
- PKC logging now uses Kodi log levels
- Further code optimizations
version 2.12.24:
- version 2.12.23 for everyone
version 1.8.12:
- Fix library sync crashing trying to display an error
version 2.12.23 (beta only):
- Fix Alexa and RuntimeError: dictionary keys changed during iteration
- Fix a rare AttributeError when using playlists
version 1.8.11:
- version 1.8.10 for everybody
version 2.12.22:
- version 2.12.20 and 2.12.21 for everyone
version 1.8.10 (beta only):
- Vastly improve sync speed for music
- Never show library sync dialog if media is playing
- Improvements to sync dialog
- Fix stop synching if path not found
- Resume aborted sync on PKC settings change
- Don't quit library sync if failed repeatedly
- Verify path for every Plex library on install sync
- More descriptive downloadable subtitles
- More code fixes and optimization
version 1.8.9
- Fix playback not starting in some circumstances
- Deactivate some annoying popups on install
version 1.8.8
- Fix playback not starting in some circumstances
- Fix first artist "missing" tag (Reset your DB!)
- Update Czech translation
version 1.8.7 (beta only):
- Some fixes to playstate reporting, thanks @RickDB
- Add Kodi info screen for episodes in context menu
- Fix PKC asking for trailers not working
- Fix PKC not automatically updating
version 1.8.6:
- Portuguese translation, thanks @goncalo532
- Updated other translations
version 1.8.5:
- version 1.8.4 for everyone
version 1.8.5:
- version 1.8.4 for everyone
version 1.8.4 (beta only):
- Plex cloud should now work: Request pictures with transcoding API
- Fix Plex companion feedback for Android
version 2.12.21 (beta only):
- Switch to new websocket implementation
- Hopefully fix RuntimeError: no add-on id "plugin.video.plexkodiconnect"
- Update translations
version 1.8.3:
- Fix Kodi playlists being empty
version 2.12.20 (beta only):
- Add information to PKC settings for background sync and Alexa whether a connection has been successfully made
version 1.8.2:
- Choose to replace user ratings with the number of available versions of a media file
- More collection artwork: use TheMovieDB art
- Support new Companion command "refreshPlayQueue"
- Use https for TheMovieDB
version 2.12.19:
- 2.12.17 and 2.12.18 for everyone
- Rename skip intro skin file
version 2.12.18 (beta only):
- Quickly sync recently watched items before synching the playstates of the entire Plex library
- Improve logging for websocket JSON loads
version 2.12.17 (beta only):
- Sync name and user rating of a TV show season to Kodi
- Fix rare TypeError: expected string or buffer on playback start
version 2.12.16:
- versions 2.12.14 and 2.12.15 for everyone
version 2.12.15 (beta only):
- Fix skip intros sometimes not working due to a RuntimeError
- Update translations
version 1.8.1:
- Fix library sync crash due to UnicodeDecodeError
- Fix fanart for collections
- Comply with themoviedb.org terms of use
- Add some translations
version 2.12.14:
- Add skip intro functionality
version 1.8.0
Featuring:
- Major music overhaul: Direct Paths should now work! Many thanks @Memesa for the pointers! Don't forget to reset your database
- Big transcoding overhaul
- Many Plex Companion fixes
- Add support to Kodi 18.0-alpha1 (thanks @CotzaDev)
version 2.12.13:
- Fix KeyError: u'game' if Plex Arcade has been activated
- Fix AttributeError: 'App' object has no attribute 'threads' when sync is cancelled
version 1.7.22 (beta only)
- Fix playback stop not being recognized by the PMS
- Better way to sync progress to another account
version 2.12.12:
- Hopefully fix rare case when sync would get stuck indefinitely
- Fix ValueError: invalid literal for int() for invalid dates sent by Plex
- version 2.12.11 for everyone
version 1.7.21 (beta only)
- Fix Playback and watched status not syncing
- Fix PKC syncing progress to wrong account
- Warn user if a xml cannot be parsed
version 2.12.11 (beta only):
- Fix PKC not auto-picking audio/subtitle stream when transcoding
- Fix ValueError when deleting a music album
- Fix OSError: Invalid argument when Plex returns an invalid timestamp
version 1.7.20 (beta only)
- Fix for Windows usernames with non-ASCII chars
- Companion: Fix TypeError
- Use SSL settings when checking server connection
- Fix TypeError when PMS connection lost
- Increase timeout
version 2.12.10:
- Fix pictures from Plex picture libraries not working/displaying
version 1.7.19 (beta only)
- Big code refactoring
- Many Plex Companion fixes
- Fix WindowsError or alike when deleting video nodes
- Remove restart on first setup
- Only set advancedsettings tweaks if Music enabled
version 2.12.9:
- Fix Local variable 'user' referenced before assignement
version 1.7.18 (beta only)
- Fix OperationalError when resetting PKC
- Fix possible OperationalErrors
- Companion: ensure sockets get closed
- Fix TypeError for Plex Companion
- Update Czech
version 2.12.8:
- version 2.12.7 for everyone
version 1.7.17 (beta only)
- Don't add media by other add-ons to queue
- Fix KeyError for Plex Companion
- Repace Kodi mkdirs with os.makedirs
- Use xbmcvfs exists instead of os.path.exists
version 2.12.7 (beta only):
- Fix PKC suddenly using main Plex user's credentials, e.g. when the PMS address changed
- Fix missing Kodi tags for movie collections/sets
version 1.7.16 (beta only)
- Fix PKC complaining about files not found
- Fix multiple subtitles per language not showing
- Update Czech translation
- Fix too many arguments when marking 100% watched
- More small fixes
version 1.7.15 (beta only)
- Fix companion for "Playback via PMS"
- Change sleeping behavior for playqueue client
- Plex Companion: add itemType to playstate
- Less logging
version 1.7.14 (beta only)
- Fix TypeError, but for real now
version 1.7.13 (beta only)
- Fix TypeError with AdvancedSettings.xml missing
version 1.7.12 (beta only)
- Major music overhaul: Direct Paths should now work! Many thanks @Memesa for the pointers! Don't forget to reset your database
- Some Plex Companion fixes
- Fix UnicodeDecodeError on user switch
- Remove link to Crowdin.com
- Update Readme
version 1.7.11 (beta only)
- Add support to Kodi 18.0-alpha1 (thanks @CotzaDev)
- Fix PKC not storing network credentials correctly
version 1.7.10 (beta only)
- Avoid xbmcvfs entirely; use encoded paths
- Update Czech translation
version 1.7.9 (beta only)
- Big transcoding overhaul
- Fix for not detecting external subtitle language
- Change Plex transcoding profile to Android
- Use Kodi video cache setting for transcoding
- Fix TheTVDB ID for TV shows
- Account for missing IMDB ids for movies
- Account for missing TheTVDB ids
- Fix UnicodeDecodeError on user switch
- Update English, Spanish and German
version 1.7.8 (beta only)
- Fix IMDB id for movies (resync by going to the PKC settings, Advanced, then Repair Local Database)
- Increase timeouts for PMS, should fix some connection issues
- Move translations to new strings.po system
- Fix some TypeErrors
- Some code refactoring
version 1.7.7
- Chinese Traditional, thanks @old2tan
- Chinese Simplified, thanks @everdream
- Browse by folder: also sort by Date Added
- Update addon.xml
version 1.7.6
- Hotfix: Revert Cache missing artwork on PKC startup. This should help with slow PKC startup, videos not being started, lagging PKC, etc.
version 1.7.5
- Dutch translation, thanks @mvanbaak
version 1.7.4 (beta only)
- Show menu item only for appropriate Kodi library: Be careful to start video content through Videos - Video Addons - ... and pictures through Pictures - Picture Addons - ...
- Fix playback error popup when using Alexa
- New Italian translations, thanks @nikkux, @chicco83
version 2.12.6:
- Fix rare KeyError when using PKC widgets
- Fix suspension of artwork caching and PKC becoming unresponsive
- Update translations
- Rewire Kodi ListItem stuff
- Fix TypeError for setting ListItem streams
- Fix Kodi setContent for images
- Fix AttributeError due to missing Kodi sort methods
- Versions 2.12.4 and 2.12.5 for everyone
version 1.7.3 (beta only)
- Fix KeyError for channels if no media streams
- Move plex node navigation, playback to main thread
- Fix TypeError for malformed browsing xml
- Fix IndexError if we can't get a valid xml from PMS
- Pass 'None' instead of empty string in url args
version 2.12.5 (beta only):
- Greatly improve matching logic for The Movie Database if Plex does not provide an appropriate id
- Fix high transcoding resolutions not being available for Win10
- Fix rare playback progress report failing and KeyError: u'containerKey'
- Fix rare KeyError: None when trying to sync playlists
- Fix TypeError when canceling Plex sync section dialog
version 1.7.2
- Fix for some channels not starting playback
version 2.12.4 (beta only):
- Hopefully fix freeze during sync: Don't assign multiple sets/collections for a specific movie
- Support metadata provider ids (e.g. for IMDB) for the new Plex Movie Agent
version 1.7.1
- Fix Alexa not doing anything
version 2.12.3:
- Fix playback failing due to caching of subtitles with non-ascii chars
- Fix ValueError: invalid literal for int() with base 10 during show sync
- Fix UnboundLocalError when certain Plex sections are deleted or being un-synched
- New method to install PlexKodiConnect directly via an URL. You thus do not need to upload a ZIP file to Kodi anymore.
version 1.7.0
- Amazon Alexa support! Be sure to check the Plex Alexa forum first if you encounter issues; there are still many bugs completely unrelated to PKC
- Plex Channels!
- Browse video nodes by folder/path
- Fix IndexError for playqueues
version 2.12.2:
- version 2.12.0 and 2.12.1 for everyone
- Fix regression: sync dialog not showing up when it should
version 2.12.1 (beta only):
- Fix PKC shutdown on Kodi profile switch
- Fix Kodi content type for images/photos
- Added support for custom set of safe characters when escaping paths (thanks @geropan)
- Revert "Don't allow spaces in devicename"
- Fix sync dialog showing in certain cases even though user opted out
version 2.12.0 (beta only):
- Fix websocket threads; enable PKC background sync for all Plex Home users!
- Fix PKC incorrectly marking a video as unwatched if an external player has been used
- Update translations
- Code optimization</news>
version 2.11.7:
- Fix PKC crashing on devices running Microsoft UWP, e.g. XBox
version 2.11.6:
- Fix rare sync crash when queue was full
- Set "Auto-adjust transcoding quality" to false by default
version 2.11.5:
- Versions 2.11.0-2.11.4 for everyone
version 2.11.4 (beta only):
- Fix another TypeError: 'NoneType' object has no attribute '__getitem__', e.g. when trying to play trailers
version 2.11.3 (beta only):
- Fix TypeError: 'NoneType' object has no attribute '__getitem__', e.g. when displaying albums
version 2.11.2 (beta only):
- Refactor direct and add-on paths. Enables use of Plex music playlists synched to Kodi
version 2.11.1 (beta only):
- Rewire the set-up of audio and subtitle streams, esp. before starting a transcoding session. Fixes playback not starting at all
version 2.11.0 (beta only):
- Fix PKC not burning in (and thus not showing) subtitles when transcoding
- When transcoding, only let user choose to burn-in subtitles that can't be displayed otherwise by Kodi
- Improve PKC automatically connecting to local PMS
- Ensure that our only video transcoding target is h264
- Fix adjusted subtitle size not working when burning in subtitles
- Fix regression: burn-in subtitles picking up the last user setting instead of the current one
</news>
</extension>
</addon>

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
###############################################################################
from __future__ import absolute_import, division, unicode_literals
from sys import listitem
from urllib import urlencode
@ -39,9 +40,10 @@ def main():
'kodi_id': kodi_id,
'kodi_type': kodi_type
}
while window.getProperty('plex_command'):
while window.getProperty('plexkodiconnect.command'):
sleep(20)
window.setProperty('plex_command', 'CONTEXT_menu?%s' % urlencode(args))
window.setProperty('plexkodiconnect.command',
'CONTEXT_menu?%s' % urlencode(args))
if __name__ == "__main__":

View file

@ -1,49 +1,22 @@
# -*- coding: utf-8 -*-
###############################################################################
from __future__ import absolute_import, division, unicode_literals
import logging
from os import path as os_path
from sys import path as sys_path, argv
from sys import argv
from urlparse import parse_qsl
from xbmc import translatePath, sleep, executebuiltin
from xbmcaddon import Addon
from xbmcgui import ListItem
from xbmcplugin import setResolvedUrl
import xbmc
import xbmcgui
import xbmcplugin
_addon = Addon(id='plugin.video.plexkodiconnect')
try:
_addon_path = _addon.getAddonInfo('path').decode('utf-8')
except TypeError:
_addon_path = _addon.getAddonInfo('path').decode()
try:
_base_resource = translatePath(os_path.join(
_addon_path,
'resources',
'lib')).decode('utf-8')
except TypeError:
_base_resource = translatePath(os_path.join(
_addon_path,
'resources',
'lib')).decode()
sys_path.append(_base_resource)
from resources.lib import entrypoint, utils, transfer, variables as v, loghandler
from resources.lib.tools import unicode_paths
###############################################################################
import entrypoint
from utils import window, reset, passwords_xml, language as lang, dialog, \
plex_command
from pickler import unpickle_me, pickl_window
from PKC_listitem import convert_PKC_to_listitem
import variables as v
###############################################################################
import loghandler
loghandler.config()
log = logging.getLogger('PLEX.default')
LOG = logging.getLogger('PLEX.default')
###############################################################################
@ -54,9 +27,15 @@ class Main():
# MAIN ENTRY POINT
# @utils.profiling()
def __init__(self):
log.debug('Full sys.argv received: %s' % argv)
LOG.debug('Full sys.argv received: %s', argv)
# Parse parameters
params = dict(parse_qsl(argv[2][1:]))
arguments = unicode_paths.decode(argv[2])
path = unicode_paths.decode(argv[0])
# Ensure unicode
for key, value in params.iteritems():
params[key.decode('utf-8')] = params.pop(key)
params[key] = value.decode('utf-8')
mode = params.get('mode', '')
itemid = params.get('id', '')
@ -66,108 +45,117 @@ class Main():
elif mode == 'plex_node':
self.play()
elif mode == 'ondeck':
entrypoint.getOnDeck(itemid,
params.get('type'),
params.get('tagname'),
int(params.get('limit')))
elif mode == 'recentepisodes':
entrypoint.getRecentEpisodes(itemid,
params.get('type'),
params.get('tagname'),
int(params.get('limit')))
elif mode == 'nextup':
entrypoint.getNextUpEpisodes(params['tagname'],
int(params['limit']))
elif mode == 'inprogressepisodes':
entrypoint.getInProgressEpisodes(params['tagname'],
int(params['limit']))
elif mode == 'browseplex':
entrypoint.browse_plex(key=params.get('key'),
plex_section_id=params.get('id'))
plex_type=params.get('plex_type'),
section_id=params.get('section_id'),
synched=params.get('synched') != 'false',
prompt=params.get('prompt'),
query=params.get('query'))
elif mode == 'getsubfolders':
entrypoint.GetSubFolders(itemid)
elif mode == 'show_section':
entrypoint.show_section(params.get('section_index'))
elif mode == 'watchlater':
entrypoint.watchlater()
elif mode == 'channels':
entrypoint.channels()
entrypoint.browse_plex(key='/channels/all')
elif mode == 'search':
# "Search"
entrypoint.browse_plex(key='/hubs/search',
args={'includeCollections': 1,
'includeExternalMedia': 1},
prompt=utils.lang(137),
query=params.get('query'))
elif mode == 'route_to_extras':
# Hack so we can store this path in the Kodi DB
handle = ('plugin://%s?mode=extras&plex_id=%s'
% (v.ADDON_ID, params.get('plex_id')))
if xbmcgui.getCurrentWindowId() == 10025:
# Video Window
xbmc.executebuiltin('Container.Update(\"%s\")' % handle)
else:
xbmc.executebuiltin('ActivateWindow(videos, \"%s\")' % handle)
elif mode == 'extras':
entrypoint.extras(plex_id=params.get('plex_id'))
elif mode == 'settings':
executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID)
xbmc.executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID)
elif mode == 'enterPMS':
entrypoint.enterPMS()
LOG.info('Request to manually enter new PMS address')
transfer.plex_command('enter_new_pms_address')
elif mode == 'reset':
reset()
transfer.plex_command('RESET-PKC')
elif mode == 'togglePlexTV':
entrypoint.togglePlexTV()
elif mode == 'resetauth':
entrypoint.resetAuth()
LOG.info('Toggle of Plex.tv sign-in requested')
transfer.plex_command('toggle_plex_tv_sign_in')
elif mode == 'passwords':
passwords_xml()
from resources.lib.windows import direct_path_sources
direct_path_sources.start()
elif mode == 'switchuser':
entrypoint.switchPlexUser()
LOG.info('Plex home user switch requested')
transfer.plex_command('switch_plex_user')
elif mode in ('manualsync', 'repair'):
if window('plex_online') != 'true':
# Server is not online, do not run the sync
dialog('ok', lang(29999), lang(39205))
log.error('Not connected to a PMS.')
else:
if mode == 'repair':
log.info('Requesting repair lib sync')
plex_command('RUN_LIB_SCAN', 'repair')
elif mode == 'manualsync':
log.info('Requesting full library scan')
plex_command('RUN_LIB_SCAN', 'full')
if mode == 'repair':
LOG.info('Requesting repair lib sync')
transfer.plex_command('repair-scan')
elif mode == 'manualsync':
LOG.info('Requesting full library scan')
transfer.plex_command('full-scan')
elif mode == 'texturecache':
log.info('Requesting texture caching of all textures')
plex_command('RUN_LIB_SCAN', 'textures')
LOG.info('Requesting texture caching of all textures')
transfer.plex_command('textures-scan')
elif mode == 'chooseServer':
entrypoint.chooseServer()
elif mode == 'refreshplaylist':
log.info('Requesting playlist/nodes refresh')
plex_command('RUN_LIB_SCAN', 'views')
LOG.info("Choosing PMS server requested, starting")
transfer.plex_command('choose_pms_server')
elif mode == 'deviceid':
self.deviceid()
elif mode == 'fanart':
log.info('User requested fanarttv refresh')
plex_command('RUN_LIB_SCAN', 'fanart')
LOG.info('User requested fanarttv refresh')
transfer.plex_command('fanart-scan')
elif '/extrafanart' in argv[0]:
plexpath = argv[2][1:]
elif '/extrafanart' in path:
plexpath = arguments[1:]
plexid = itemid
entrypoint.getExtraFanArt(plexid, plexpath)
entrypoint.getVideoFiles(plexid, plexpath)
entrypoint.extra_fanart(plexid, plexpath)
entrypoint.get_video_files(plexid, plexpath)
# Called by e.g. 3rd party plugin video extras
elif ('/Extras' in argv[0] or '/VideoFiles' in argv[0] or
'/Extras' in argv[2]):
elif ('/Extras' in path or '/VideoFiles' in path or
'/Extras' in arguments):
plexId = itemid or None
entrypoint.getVideoFiles(plexId, params)
entrypoint.get_video_files(plexId, params)
elif mode == 'playlists':
entrypoint.playlists(params.get('content_type'))
elif mode == 'hub':
entrypoint.hub(params.get('content_type'))
elif mode == 'select-libraries':
LOG.info('User requested to select Plex libraries')
transfer.plex_command('select-libraries')
elif mode == 'refreshplaylist':
LOG.info('User requested to refresh Kodi playlists and nodes')
transfer.plex_command('refreshplaylist')
else:
entrypoint.doMainListing(content_type=params.get('content_type'))
entrypoint.show_main_menu(content_type=params.get('content_type'))
@staticmethod
def play():
@ -176,44 +164,46 @@ class Main():
"""
request = '%s&handle=%s' % (argv[2], HANDLE)
# Put the request into the 'queue'
plex_command('PLAY', request)
transfer.plex_command('PLAY-%s' % request)
if HANDLE == -1:
# Handle -1 received, not waiting for main thread
return
# Wait for the result
while not pickl_window('plex_result'):
sleep(50)
result = unpickle_me()
if result is None:
log.error('Error encountered, aborting')
dialog('notification',
heading='{plex}',
message=lang(30128),
icon='{error}',
time=3000)
setResolvedUrl(HANDLE, False, ListItem())
elif result.listitem:
listitem = convert_PKC_to_listitem(result.listitem)
setResolvedUrl(HANDLE, True, listitem)
# Wait for the result from the main PKC thread
result = transfer.wait_for_transfer(source='main')
if result is True:
xbmcplugin.setResolvedUrl(HANDLE, False, xbmcgui.ListItem())
# Tell main thread that we're done
transfer.send(True, target='main')
else:
# Received a xbmcgui.ListItem()
xbmcplugin.setResolvedUrl(HANDLE, True, result)
@staticmethod
def deviceid():
deviceId_old = window('plex_client_Id')
from clientinfo import getDeviceId
window = xbmcgui.Window(10000)
deviceId_old = window.getProperty('plex_client_Id')
from resources.lib import clientinfo
try:
deviceId = getDeviceId(reset=True)
deviceId = clientinfo.getDeviceId(reset=True)
except Exception as e:
log.error('Failed to generate a new device Id: %s' % e)
dialog('ok', lang(29999), lang(33032))
LOG.error('Failed to generate a new device Id: %s' % e)
utils.messageDialog(utils.lang(29999), utils.lang(33032))
else:
log.info('Successfully removed old device ID: %s New deviceId:'
LOG.info('Successfully removed old device ID: %s New deviceId:'
'%s' % (deviceId_old, deviceId))
# 'Kodi will now restart to apply the changes'
dialog('ok', lang(29999), lang(33033))
executebuiltin('RestartApp')
utils.messageDialog(utils.lang(29999), utils.lang(33033))
xbmc.executebuiltin('RestartApp')
if __name__ == '__main__':
log.info('%s started' % v.ADDON_ID)
Main()
log.info('%s stopped' % v.ADDON_ID)
LOG.info('%s started' % v.ADDON_ID)
try:
v.database_paths()
except RuntimeError as err:
# Database does not exists
LOG.error('The current Kodi version is incompatible')
LOG.error('Error: %s', err)
else:
Main()
LOG.info('%s stopped' % v.ADDON_ID)

Binary file not shown.

View file

@ -1,20 +1,21 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2017
# Michal Kuncl <michal.kuncl@gmail.com>, 2017
# Michal Kuncl <michal.kuncl@gmail.com>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: PlexKodiConnect\n"
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Michal Kuncl <michal.kuncl@gmail.com>, 2017\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Michal Kuncl <michal.kuncl@gmail.com>, 2020\n"
"Language-Team: Czech (Czech Republic) (https://www.transifex.com/croneter/teams/73837/cs_CZ/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: cs_CZ\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
# Add-on settings
msgctxt "#29999"
@ -44,6 +45,13 @@ msgstr ""
"Varování: Máte v Kodi zapnuté nastavení \"Automaticky přehrát další video\"."
" Toto může narušit funkčnost PKC. Deaktivovat?"
msgctxt "#30004"
msgid ""
"The Kodi webserver is needed for artwork caching. PKC already set a strong, "
"random password automatically if you haven't done so already. Please confirm"
" the next dialog that you want to enable the webserver now with Yes."
msgstr ""
msgctxt "#30005"
msgid "Username: "
msgstr "Uživ. jméno: "
@ -51,32 +59,33 @@ msgstr "Uživ. jméno: "
# Sync notification displayed if there is still artwork to be cached to Kodi
msgctxt "#30006"
msgid "Caching %s Plex images"
msgstr ""
msgstr "Ukládám %s obrázků z Plexu"
# Sync notification displayed if syncing of major artwork is done
msgctxt "#30007"
msgid "Plex image caching done"
msgstr ""
msgstr "Ukládání obrázků do mezipaměti dokončeno."
# PKC settings artwork: Enable notifications for artwork image sync
msgctxt "#30008"
msgid "Enable notifications for image caching"
msgstr ""
msgstr "Povolit upozornění na ukládání obrázků do mezipaměti"
# PKC settings artwork: Enable image caching during Kodi playback
msgctxt "#30009"
msgid "Enable image caching during Kodi playback (restart Kodi!)"
msgstr ""
"Povolit ukládání obrázků do mezipaměti při přehrávání (restartujte Kodi!)"
# PKC settings - Artwork
msgctxt "#30010"
msgid "Approximate progress"
msgstr ""
msgstr "Odhad průběhu"
# PKC settings - Artwork
msgctxt "#30011"
msgid "Plex artwork (posters and backgrounds) left to cache:"
msgstr ""
msgid "Artwork left to cache:"
msgstr "Obrázků k uložení do mezipaměti:"
# Button text
msgctxt "#30012"
@ -92,10 +101,10 @@ msgctxt "#30014"
msgid "Connection"
msgstr "Připojení"
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgstr ""
msgid "Fanart download already running"
msgstr "Stahování obrázků již běží"
msgctxt "#30016"
msgid "Device Name"
@ -109,18 +118,33 @@ msgstr "Neověřeno u PMS"
# Sync notification displayed for the number of fanart.tv lookups left
msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr ""
msgstr "Vyhledávám %s položek na FanartTV"
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr ""
msgstr "Vyhledávání na FanartTV dokončeno"
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr "Synchronizovat playlisty Plexu (restartujte Kodi!)"
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr "Synchronizovat pouze konktrétní playlisty do Kodi"
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr "Rozšířené"
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr "Synchronizovat pouze konkrétní playlisty do Plexu"
msgctxt "#30024"
msgid "Username"
msgstr "Uživ. jméno"
@ -129,6 +153,29 @@ msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr "Zobrazit zprávu při odpojení PMS"
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr "Předpona jména playlistu v Kodi pro synchronizaci"
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr "Předpona jména playlistu v Plexu pro synchronizaci"
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "Stahování obrázků PKC dokončeno"
# Warning shown when PKC switches to the Kodi default skin Estuary
msgctxt "#30029"
msgid ""
"To ensure a smooth PlexKodiConnect experience, it is HIGHLY recommended to "
"use Kodi's default skin \"Estuary\" for initial set-up and for possible "
"database resets. Continue?"
msgstr ""
msgctxt "#30030"
msgid "Port Number"
msgstr "Číslo portu"
@ -228,6 +275,10 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr "Kvalita videa při překódování"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr "Automaticky upravit kvalitu překódování (deaktivujte pro Chromecast)"
msgctxt "#30165"
msgid "Direct Play"
msgstr "Přímé přehrávání"
@ -284,6 +335,46 @@ msgctxt "#30198"
msgid "Search"
msgstr "Hledání"
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid ""
"In the following window, enter the server's hostname (or IP) where your Plex"
" media resides. Mind the case!"
msgstr ""
"Do následujícího okna zadejte jméno serveru (nebo IP adresu), na kterém běží"
" Plex Media Server. Pozor na velká písmena!"
# For setting up direct paths and adding network credentials - input window
# for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr "Zadejte jméno serveru (nebo IP adresu)"
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid ""
"In the following window, enter the network protocol you would like to use. "
"This is likely 'smb'."
msgstr ""
"Do následujícího okna zadejte síťový protokol, který chcete použít. "
"Pravděpodobně se jedná o 'smb'."
# For setting up direct paths and adding network credentials - input window
# protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr "Zadejte síťový protokol"
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr "Jméno, nebo IP adresa hostitele '{0}' není platný."
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr "Protocol '{0}', není podporován."
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -397,6 +488,14 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr "Nastavení Plex Serveru"
msgctxt "#30417"
msgid ""
"Could not change the Kodi settings file {0}. PKC might not work correctly. "
"Error: {1}"
msgstr ""
"Nemohu změnit soubor nastavení Kodi - {0}. PKC nemusí fungovat správně. "
"Chyba: {1}"
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -407,10 +506,15 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr "Klientský SSL certifikát"
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
msgstr "[COLOR yellow]Resetovat pokusy o přihlášení[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr "Synchronizovat obrázky Plexu z PMS (doporučeno)"
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr "Nemohu ověřit SSL certifikát. Pro řešení se podívejte do {0}."
# PKC Settings, category name
msgctxt "#30506"
@ -444,13 +548,18 @@ msgstr "Režim přehrávání"
# PKC Settings - Artwork
msgctxt "#30512"
msgid "Cache all artwork for a smooth Kodi experience"
msgstr ""
msgstr "Ukládat všechny obrázky do mezipaměti pro hladší běh Kodi"
# PKC Settings - Artwork
msgctxt "#30513"
msgid "Limit artwork cache threads (recommended for rpi)"
msgstr "Omezit vlákna mezipaměti obrázků (doporučeno pro rpi)"
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr "Místo přehrání ukázek zobrazit všechny bonusy"
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -463,8 +572,8 @@ msgstr "Přehrávání"
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgstr "[COLOR yellow]Zadejte přihlašovací údaje k síťi[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr "Nastavit přihlašovací údaje pro přímé cesty a přímé přehrávání"
# PKC Settings - Playback
msgctxt "#30518"
@ -491,6 +600,22 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "Vynutit překódování H265/HEVC"
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr ""
"Také zobrazovat průběh synchronizace stavu přehrávání a uživatelských dat"
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Zvolte knihovny Plexu k synchronizaci"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -525,6 +650,8 @@ msgstr "Zprávy serveru"
msgctxt "#30535"
msgid "Generate a new unique Plex device Id (e.g. to clone Kodi)"
msgstr ""
"Vygenerovat nový unikátní identifikátor zařízení Plexu (např. při kopii "
"Kodi)"
# PKC Settings - Connection
msgctxt "#30536"
@ -539,7 +666,7 @@ msgstr "PŘI KAŽDÉ ZMĚNĚ MUSÍTE RESTARTOVAT KODI"
# PKC Settings warning
msgctxt "#30538"
msgid "Manual complete reset of Kodi database necessary, see \"Advanced\""
msgstr ""
msgstr "Je vyžadován ruční reset databaze. Viz \"rozšířené\""
# PKC Settings - Artwork
msgctxt "#30539"
@ -553,14 +680,19 @@ msgstr "Stahovat obrázky filmových kolekcí z FanArtTV"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Nepožadovat výběr proudu nebo kvality"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr "Vždy vybrat nejlepší kvalitu ukázek"
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr "Upřednostňovat obrázky z Kodi pro kolekce/sady"
msgctxt "#30544"
msgid "Artwork"
msgstr "Obrázky"
@ -570,6 +702,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Vynutit překódování obrázků"
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr ""
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr ""
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr ""
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -590,15 +737,29 @@ msgctxt "#33003"
msgid "Server is online"
msgstr "Server je online"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr "Překódování vynucené PMS"
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr "Přímé streamování vynucené PMS"
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "Neplatné uživatelské jméno nebo heslo"
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgstr ""
"Proběhlo moc neúspěšných pokusů o ověření. Proveďte reset v nastavení."
msgid "User is unauthorized for server {0}"
msgstr "Uživatel není ověřen u serveru {0}"
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr "Je nám líto, ale plex.tv neposkytl platný seznam uživatelů."
# Dialog before playback
msgctxt "#33013"
@ -653,7 +814,7 @@ msgstr "Velikost vpálených titulků"
# PKC Settings - Sync
msgctxt "#39003"
msgid "Number of simultaneous download threads"
msgstr ""
msgstr "Počet souběžných vláken stahování"
# PKC Settings - Plex
msgctxt "#39004"
@ -726,19 +887,17 @@ msgstr ""
# PKC Settings - Advanced
msgctxt "#39018"
msgid "Repair the Kodi database (force update all content)"
msgstr ""
msgstr "Opravit databázi Kodi (vynutit aktualizaci všeho obsahu)"
# PKC Settings - Advanced
msgctxt "#39019"
msgid "Reset the Kodi database and optionally reset PlexKodiConnect"
msgstr ""
msgstr "Vyresetovat databázi Kodi a případně i nastavení PlexKodiConnect"
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgstr ""
"[COLOR yellow]Nyní umístit všechny obrázky do texturové mezipaměti "
"Kodi[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr "Nyní umístit všechny obrázky do texturové mezipaměti Kodi"
# Appended to a listed PMS if it is in the same LAN network as PKC
msgctxt "#39022"
@ -821,6 +980,16 @@ msgstr ""
"Nahradit cesty /volume1/media nebo \\myserver\\media za vlastní cesty "
"smb://NAS/mystuff"
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr "Nahrazovat speciální znaky v cestě (např. z mezery na %20)"
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -896,8 +1065,8 @@ msgstr "Nic nefunguje? Zkuste plný reset!"
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgstr "[COLOR yellow]Zvolte Plex server ze seznamu[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr "Zvolte Plex server ze seznamu"
# PKC Settings - Sync
msgctxt "#39051"
@ -926,19 +1095,17 @@ msgstr "Vyhledávám Plex servery"
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgstr "Použito při synchronizaci a při pokusu o přímé přehrávání"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
"Použito při synchronizaci a při pokusu o přehrávání přes přímé cesty. "
"Restartujte Kodi po změně!"
# PKC Settings, category name
msgctxt "#39057"
msgid "Customize Paths"
msgstr "Přizpůsobit cesty"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Rozšířit seriály na obrazovce \"Aktuální\" na všechny seriály"
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -978,12 +1145,8 @@ msgstr "Vynutit obnovení vzhledu Kodi po skončení přehrávání"
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr ""
"Naposledy přidané: Zobrazovat také už shlédnuté filmy (Obnovte playlisty "
"Plexu!)"
msgid "Recently Added: Also show already watched movies"
msgstr "Naposledy přidané: Zobrazovat také shlédnuté filmy"
# PKC Settings - Connection
msgctxt "#39067"
@ -992,8 +1155,8 @@ msgstr "Váš současný Plex Media Server:"
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgstr "[COLOR yellow]Ručně zadat adresu Plex Media Serveru[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr "Ručně zadat adresu Plex Media Serveru"
# PKC Settings - Connection
msgctxt "#39069"
@ -1010,6 +1173,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Současný stav plex.tv:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1020,10 +1188,10 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Seriály"
# PKC Settings - Playback
# PKC Settings - Sync
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgstr "Vždy použít výchozí titulky Plexu, pokud je to možné"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
@ -1037,8 +1205,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgstr "Počet položek pro zobrazení ve widgetech (např. \"Aktuální\")"
msgid "Maximum number of videos to show in widgets"
msgstr "Maximální počet videí zobrazovaných ve widgetech"
# PKC Settings - Plex
msgctxt "#39078"
@ -1053,6 +1221,68 @@ msgid ""
msgstr ""
"Plex Companion nemůže otevřít GDM port. Prosím změňte ho v nastavení PKC."
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
# identical!
msgctxt "#39080"
msgid ""
"Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if "
"you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
"Použít cesty doplňku (výchozí, snadné), nebo přímé cesty? Pokud si nejste "
"jistí, vyberte cesty doplňku. Pokud nastavíte přímé cesty špatně, tak nebude"
" PKC fungovat!"
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr "Cesty doplňku"
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr "Přímé cesty"
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr "Zadejte IP adresu, nebo URL PMS"
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr "Zadejte port PMS"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr "Znovu načíst Kodi pro aplikování nastavení níže"
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr ""
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr "Odhlásit uživatele Plex Home "
@ -1061,10 +1291,6 @@ msgctxt "#39201"
msgid "Settings"
msgstr "Nastavení"
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr "Obnovit playlisty Plexu"
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr "Provést ruční synchronizaci knihovny"
@ -1092,10 +1318,8 @@ msgstr "Nemohu resetovat PKC. Zkuste restartovat Kodi."
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgstr ""
"[COLOR yellow]Přepnout přihlášení k plex.tv login (přihlásit nebo "
"odhlásit)[/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr "Přepnout přihlášení přes plex.tv (přihlásit nebo odhlásit)"
msgctxt "#39210"
msgid "Not yet connected to Plex Server"
@ -1105,21 +1329,19 @@ msgctxt "#39211"
msgid "Watch later"
msgstr "Shlédnout později"
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by
# e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgstr "je offline"
msgid "{0} offline"
msgstr "{0} je offline"
msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "Zadejte IP adresu nebo URL vašeho Plex Media Serveru. Např.:"
msgctxt "#39217"
msgid ""
"Does your Plex Media Server support SSL connections? (https instead of "
"http)?"
msgstr ""
"Podporuje Váš Plex Media Server připojení přes SSL? (https místo http)?"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr "Použít připojení HTTPS (SSL)? Odpověď by měla nejspíš ano."
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1139,8 +1361,8 @@ msgid "plex.tv toggle successful"
msgstr "přepnutí plex.tv úspěšné"
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgstr "[COLOR yellow]Teď vyhledat chybějící obrázky na FanartTV[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr "Nyní vyhledat chybějící obrázky na FanartTV"
msgctxt "#39223"
msgid ""
@ -1168,11 +1390,26 @@ msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr "Přihlášeno k plex.tv"
# Message in the PKC settings to display the plex.tv username. Leave the colon
# :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgstr "Uživatel plexu:"
msgid "Plex admin user"
msgstr "Správce Plexu"
# Error message if user could not log in; the actual user name will be
# appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr "Nezdařilo se přihlášení přes plex.tv pro uživatele"
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr "Přihlášený uživatel Plex Home"
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr "Změnit uživatele Plex Home"
msgctxt "#39250"
msgid ""
@ -1244,11 +1481,9 @@ msgstr " nemusí fungovat správně dokud neprovedete reset databáze."
msgctxt "#39403"
msgid ""
"Cancelling the database syncing process. Current Kodi version is "
"unsupported. Please verify your logs for more info."
msgstr ""
"Ruším synchronizaci databáze. Tato verze Kodi není podporována. Pro více "
"informací se podívejte do záznamů."
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr "Tuto verzi Kodi PKC nepodporuje. Prosím projděte si fórum Plexu."
msgctxt "#39405"
msgid "Plex playlists/nodes refreshed"
@ -1258,10 +1493,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr "Nemohu obnovit Plex playlisty"
msgctxt "#39407"
msgid "Full library sync finished"
msgstr "Plná synchronizace knihovny dokončena"
msgctxt "#39408"
msgid ""
"Sync had to skip some items because they could not be processed. Kodi may be"
@ -1293,6 +1524,10 @@ msgctxt "#39501"
msgid "Collections"
msgstr "Kolekce"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr "PKC Aktuální (rychlejší)"
msgctxt "#39600"
msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1351,11 +1586,10 @@ msgctxt "#39705"
msgid "Use at your own risk"
msgstr "Používejte na vlastní nebezpečí"
# If user gets prompted to choose between several subtitles. Leave the number
# one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgstr "1 Žádné titulky"
msgid "Don't burn-in any subtitle"
msgstr "Nevpalovat žádné titulky"
# If user gets prompted to choose between several audio/subtitle tracks and
# language is unknown
@ -1409,8 +1643,8 @@ msgstr "Synchronizuji"
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgstr "položek"
msgid "Synching playlists"
msgstr "Synchronizuji playlisty"
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}!

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,14 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2017
# Croneter None <croneter@gmail.com>, 2021
#
msgid ""
msgstr ""
"Project-Id-Version: PlexKodiConnect\n"
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2017\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2021\n"
"Language-Team: German (Germany) (https://www.transifex.com/croneter/teams/73837/de_DE/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -43,6 +44,17 @@ msgstr ""
"Achtung: Kodi Einstellung \"Nächsten Video automatisch abspielen\" ist "
"aktiviert. Dies kann PKC stören. Deaktivieren?"
msgctxt "#30004"
msgid ""
"The Kodi webserver is needed for artwork caching. PKC already set a strong, "
"random password automatically if you haven't done so already. Please confirm"
" the next dialog that you want to enable the webserver now with Yes."
msgstr ""
"Der Kodi-Webserver wird für Artwork-Caching benötigt. PKC hat bereits "
"automatisch ein starkes, zufälliges Passwort gesetzt, falls Sie dies nicht "
"schon getan haben. Bitte bestätigen Sie den nächsten Dialog mit Ja, dass der"
" Webserver aktiviert werden kann."
msgctxt "#30005"
msgid "Username: "
msgstr "Benutzername: "
@ -74,8 +86,8 @@ msgstr "Ungefährer Stand"
# PKC settings - Artwork
msgctxt "#30011"
msgid "Plex artwork (posters and backgrounds) left to cache:"
msgstr "Plex Bilder (Poster und Hintergründe) noch zu cachen:"
msgid "Artwork left to cache:"
msgstr "Noch zu cachende Bilder:"
# Button text
msgctxt "#30012"
@ -91,10 +103,10 @@ msgctxt "#30014"
msgid "Connection"
msgstr "Verbindung"
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgstr "Filme und Serien noch bei FanartTV nachzuschlagen:"
msgid "Fanart download already running"
msgstr "Fanart-Download läuft bereits"
msgctxt "#30016"
msgid "Device Name"
@ -110,16 +122,31 @@ msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr "%s Nachforschungen bei FanartTV"
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr "FanartTV Nachforschungen beendet"
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr "Plex Wiedergabelisten synchronisieren (Kodi neu starten!)"
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr "Nur spezifische Plex Wiedergabelisten synchronisieren"
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr "Fortgeschritten"
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr "Nur spezifische Kodi Wiedergabelisten synchronisieren"
msgctxt "#30024"
msgid "Username"
msgstr "Benutzername"
@ -128,6 +155,32 @@ msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr "Benachrichtigen, wenn PMS offline ist"
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr "Präfix der Plex Wiedergabelisten, welche synchronisiert werden"
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr "Präfix der Kodi Wiedergabelisten, welche synchronisiert werden"
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "PKC Bilder-Caching beendet"
# Warning shown when PKC switches to the Kodi default skin Estuary
msgctxt "#30029"
msgid ""
"To ensure a smooth PlexKodiConnect experience, it is HIGHLY recommended to "
"use Kodi's default skin \"Estuary\" for initial set-up and for possible "
"database resets. Continue?"
msgstr ""
"Um ein reibungsloses PlexKodiConnect-Erlebnis zu gewährleisten, wird "
"DRINGEND empfohlen, für die Ersteinrichtung und für mögliche Datenbank-"
"Resets den Standard-Skin \"Estuary\" von Kodi zu verwenden. Weiterfahren?"
msgctxt "#30030"
msgid "Port Number"
msgstr "Portnummer"
@ -227,6 +280,11 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr "Videoqualität falls Transkodierung nötig"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
"Transcoding Qualität automatisch anpassen (deaktivieren für Chromecast)"
msgctxt "#30165"
msgid "Direct Play"
msgstr "Direkte Wiedergabe"
@ -283,6 +341,49 @@ msgctxt "#30198"
msgid "Search"
msgstr "Suche"
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid ""
"In the following window, enter the server's hostname (or IP) where your Plex"
" media resides. Mind the case!"
msgstr ""
"Im nachfolgenden Fenster den Hostnamen (oder die IP) des Server eingeben, wo"
" sich die Plex Medien befinden. Achtung Gross- und Kleinschreibung!"
# For setting up direct paths and adding network credentials - input window
# for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr "Hostnamen (oder IP) des Servers eingeben"
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid ""
"In the following window, enter the network protocol you would like to use. "
"This is likely 'smb'."
msgstr ""
"Im nachfolgenden Fenster das Netzwerkprotokoll eingeben, welches genutzt "
"werden soll. Dies ist höchstwahrscheinlich 'smb'."
# For setting up direct paths and adding network credentials - input window
# protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr "Netzwerkprotokoll eingeben"
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr ""
"Der Hostname oder die IP '{0}', welche eingegeben wurde, ist unzulässig."
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr ""
"Das Netzwerkprotokoll '{0}', welches eingegeben wurde, wird nicht "
"unterstützt."
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -396,6 +497,14 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr "Einstellungen für den Plex Server"
msgctxt "#30417"
msgid ""
"Could not change the Kodi settings file {0}. PKC might not work correctly. "
"Error: {1}"
msgstr ""
"Die Kodi Datei {0} konnte leider nicht geändert werden. PKC wird wohl nicht "
"richtig funktionieren. Fehler: {1}"
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -406,10 +515,17 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr "Client SSL-Zertifikat"
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
msgstr "[COLOR yellow]Anzahl Login-Versuche zurücksetzen[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr "Plex Bilder vom Plex Medienserver synchronisieren (empfohlen)"
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr ""
"SSL-Zertifikat konnte nicht validiert werden. Bitte besuche {0} für "
"Lösungsvorschläge."
# PKC Settings, category name
msgctxt "#30506"
@ -451,6 +567,11 @@ msgid "Limit artwork cache threads (recommended for rpi)"
msgstr ""
"Limitiere Threads zur Bildsynchronisation (empfohlen für Raspberry Pi)"
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr "Alle Plex Extras anzeigen statt Trailer abspielen"
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -463,8 +584,8 @@ msgstr "Wiedergabe"
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgstr "[COLOR yellow]Netzwerk-Anmeldeinformationen eingeben[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr "Netzwerk-Anmeldeinformationen für Direct Paths und Direct Play setzen"
# PKC Settings - Playback
msgctxt "#30518"
@ -491,6 +612,23 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "h265/HEVC Codec Transkodierung erzwingen"
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr ""
"Synchronisations-Fortschritt auch für Zwischenstände und Benutzerdaten "
"anzeigen"
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Zu synchronisierende Plex Bibliotheken auswählen"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr "Intro überspringen"
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -554,14 +692,20 @@ msgstr "FanArtTV Bilder für Film-Sets/Collections herunterladen"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Nicht nachfragen, welcher Stream oder Qualität gespielt werden soll"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
"Transkodierung: Plex-Standards für Audio- und Untertitel-Streams verwenden"
# PKC Settings - Playback
msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr "Trailer immer in der besten Qualität abspielen"
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr "Kodi Bilder für Filmsammlungen/Sets bevorzugen"
msgctxt "#30544"
msgid "Artwork"
msgstr "Artwork"
@ -571,6 +715,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Bilder immer transkodieren"
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr "Ersten Videostream wählen, wenn mehrere Versionen vorhanden sind"
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr "Wer wählt den Audiotrack beim Start der Wiedergabe?"
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr "Wer wählt Untertitel beim Start der Wiedergabe?"
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -591,16 +750,31 @@ msgctxt "#33003"
msgid "Server is online"
msgstr "Server ist online"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr "PMS muss transkodieren"
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr "PMS muss Direct Streamen"
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "Ungültiger Benutzername oder Passwort"
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgid "User is unauthorized for server {0}"
msgstr "Benutzer ist für Server {0} nicht autorisiert"
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr ""
"Plex Media Server Authorisierung ist zu häufig fehlgeschlagen. In den "
"Einstellungen können die Anzahl erfolgloser Versuche zurückgesetzt werden."
"Plex.tv hat uns leider keine zulässige Liste von Plex Benutzern gesandt, "
"sorry."
# Dialog before playback
msgctxt "#33013"
@ -744,9 +918,8 @@ msgstr "Kodi Datenbank und optional PlexKodiConnect zurücksetzen"
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgstr ""
"[COLOR yellow]Alle Plex Bilder jetzt in Kodi zwischenspeichern[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr "Alle Plex Bilder jetzt in Kodi zwischenspeichern"
# Appended to a listed PMS if it is in the same LAN network as PKC
msgctxt "#39022"
@ -831,6 +1004,16 @@ msgstr ""
"Plex Pfade /volume1/medien oder \\\\meinServer\\medien mit "
"benutzerdefinierten SMB Pfaden wie smb://NAS/Filme ersetzen"
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr "Sonderzeichen im Pfad escapen (z.B. Leerzeichen zu %20)"
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr "Sichere Zeichen für http(s), dav(s) und (s)ftp urls"
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -906,8 +1089,8 @@ msgstr "Nichts funktioniert? Setze mal alles zurück!"
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgstr "[COLOR yellow]Plex Server aus Liste auswählen[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr "Plex Server aus Liste auswählen"
# PKC Settings - Sync
msgctxt "#39051"
@ -936,20 +1119,17 @@ msgstr "Suche Plex Server"
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
"Verwendet für Synchronisierung sowie beim Versuch, Direct Play zu nutzen"
"Verwendet für Synchronisierung und Direct Paths. Bei Änderungen Kodi neu "
"starten!"
# PKC Settings, category name
msgctxt "#39057"
msgid "Customize Paths"
msgstr "Pfade ändern"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Standard Plex Ansicht \"Aktuell\" auf alle TV Shows erweitern"
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -989,12 +1169,8 @@ msgstr "Kodi Skin nach Playback-Stop neu laden"
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr ""
"\"Zuletzt hinzugefügt\": gesehene Filme anzeigen (Plex Playlisten und Nodes "
"zurücksetzen!)"
msgid "Recently Added: Also show already watched movies"
msgstr "\"Zuletzt hinzugefügt\": gesehene Filme anzeigen"
# PKC Settings - Connection
msgctxt "#39067"
@ -1003,8 +1179,8 @@ msgstr "Aktueller Plex Media Server:"
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgstr "[COLOR yellow]Plex Media Server Adresse manuell eingeben[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr "Adresse des Plex Medienservers manuell eingeben"
# PKC Settings - Connection
msgctxt "#39069"
@ -1021,6 +1197,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Aktueller plex.tv Status:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr "Verbindungsstatus Hintergrund-Synchronisation:"
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1031,10 +1212,10 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Serien"
# PKC Settings - Playback
# PKC Settings - Sync
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgstr "Falls möglich, Plex Standard-Untertitel anzeigen"
msgid "Verify access to media files while synching"
msgstr "Zugriff auf Mediendateien während der Synchronisierung überprüfen"
# Pop-up during initial sync
msgctxt "#39076"
@ -1047,8 +1228,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgstr "Anzahl anzuzeigender PMS Einträge in Widgets (z.B. \"Aktuell\")"
msgid "Maximum number of videos to show in widgets"
msgstr "Maximale Anzahl anzuzeigende Videos in Widgets"
# PKC Settings - Plex
msgctxt "#39078"
@ -1064,6 +1245,68 @@ msgstr ""
"Plex Companion konnte den Update Port nicht öffnen. Bitte den Port in den "
"PKC Einstellungen ändern."
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
# identical!
msgctxt "#39080"
msgid ""
"Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if "
"you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
"Add-on Paths (Standardeinstellung, einfach) oder Direct Paths? Wählen Sie "
"Add-on Paths, wenn Sie sich nicht sicher sind. PKC wird nicht funktionieren,"
" wenn die Einstellungen für Direct Paths nicht korrekt sind!"
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr "Add-on Paths"
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr "Direct Paths"
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr "PMS IP oder URL eingeben"
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr "PMS Port eingeben"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr "Kodi neu laden um Einstellungen unten zu übernehmen"
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr "Alexa Verbindungsstatus:"
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr "Timeout - nicht verbunden"
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr "IOError - nicht verbunden"
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr "Angehalten - nicht verbunden"
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr "Managed Plex User - nicht verbunden"
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr "Plex Home Benutzer abmelden: "
@ -1072,10 +1315,6 @@ msgctxt "#39201"
msgid "Settings"
msgstr "Einstellungen"
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr "Plex Playlisten und Video Nodes aktualisieren"
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr "Manuellen Scan der Plex Bibliotheken starten"
@ -1107,8 +1346,8 @@ msgstr ""
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgstr "[COLOR yellow]plex.tv Login wechseln (ein- resp. ausloggen)[/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr "plex.tv Login ändern (ein- resp. ausloggen)"
msgctxt "#39210"
msgid "Not yet connected to Plex Server"
@ -1118,22 +1357,19 @@ msgctxt "#39211"
msgid "Watch later"
msgstr "Später ansehen"
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by
# e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgstr "ist offline"
msgid "{0} offline"
msgstr "{0} offline"
msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "Plex Media Server IP oder URL eingeben. Zum Beispiel:"
msgctxt "#39217"
msgid ""
"Does your Plex Media Server support SSL connections? (https instead of "
"http)?"
msgstr ""
"Unterstützt der Plex Media Server sichere SSL Verbindungen (https anstelle "
"von http)?"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr "HTTPS (SSL) verwenden? Die Antwort sollte wahrscheinlich ja sein."
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1153,8 +1389,8 @@ msgid "plex.tv toggle successful"
msgstr "plex.tv Wechsel OK"
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgstr "[COLOR yellow]Jetzt zusätzliche Bilder auf FanartTV suchen[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr "Jetzt zusätzliche Bilder auf FanartTV suchen"
msgctxt "#39223"
msgid ""
@ -1183,11 +1419,26 @@ msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr "Eingeloggt bei plex.tv"
# Message in the PKC settings to display the plex.tv username. Leave the colon
# :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgstr "Plex Benutzer:"
msgid "Plex admin user"
msgstr "Plex Admin Benutzer"
# Error message if user could not log in; the actual user name will be
# appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr "Login für plex.tv fehlgeschlagen für Benutzer"
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr "Eingeloggter Plex Home Benutzer"
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr "Eingeloggten Plex Home Benutzer wechseln"
msgctxt "#39250"
msgid ""
@ -1262,12 +1513,11 @@ msgstr ""
msgctxt "#39403"
msgid ""
"Cancelling the database syncing process. Current Kodi version is "
"unsupported. Please verify your logs for more info."
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
"Synchronisierung der Plex Bibliotheken wird abgebrochen. Die momentane Kodi "
"Version wird nicht unterstützt. Für weitere Informationen bitte das Kodi Log"
" konsultieren."
"Die aktuelle Version von Kodi wird nicht von PKC unterstützt. Bitte "
"konsultieren Sie das Plex Forum."
msgctxt "#39405"
msgid "Plex playlists/nodes refreshed"
@ -1277,10 +1527,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr "Aktualisierung der Wiedergabelisten/Video Nodes fehlgeschlagen"
msgctxt "#39407"
msgid "Full library sync finished"
msgstr "Alle Plex Bibliotheken aktualisiert"
msgctxt "#39408"
msgid ""
"Sync had to skip some items because they could not be processed. Kodi may be"
@ -1312,6 +1558,10 @@ msgctxt "#39501"
msgid "Collections"
msgstr "Kollektionen"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr "PKC Aktuell (schneller)"
msgctxt "#39600"
msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1372,11 +1622,10 @@ msgctxt "#39705"
msgid "Use at your own risk"
msgstr "Benutzung auf eigene Gefahr"
# If user gets prompted to choose between several subtitles. Leave the number
# one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgstr "1 Untertitel deaktivieren"
msgid "Don't burn-in any subtitle"
msgstr "Keinen Untertitel einbrennen"
# If user gets prompted to choose between several audio/subtitle tracks and
# language is unknown
@ -1430,8 +1679,8 @@ msgstr "Sync"
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgstr "Einträge"
msgid "Synching playlists"
msgstr "Synchronisiere Wiedergabelisten"
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}!

File diff suppressed because it is too large Load diff

View file

@ -36,6 +36,10 @@ msgctxt "#30003"
msgid "Warning: Kodi setting \"Play next video automatically\" is enabled. This could break PKC. Deactivate?"
msgstr ""
msgctxt "#30004"
msgid "The Kodi webserver is needed for artwork caching. PKC already set a strong, random password automatically if you haven't done so already. Please confirm the next dialog that you want to enable the webserver now with Yes."
msgstr ""
msgctxt "#30005"
msgid "Username: "
msgstr ""
@ -84,9 +88,9 @@ msgctxt "#30014"
msgid "Connection"
msgstr ""
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgid "Fanart download already running"
msgstr ""
msgctxt "#30016"
@ -103,16 +107,31 @@ msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr ""
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr ""
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr ""
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr ""
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr ""
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr ""
msgctxt "#30024"
msgid "Username"
msgstr ""
@ -121,6 +140,21 @@ msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr ""
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr ""
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr ""
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr ""
msgctxt "#30030"
msgid "Port Number"
msgstr ""
@ -220,6 +254,10 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr ""
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
msgctxt "#30165"
msgid "Direct Play"
msgstr ""
@ -276,6 +314,36 @@ msgctxt "#30198"
msgid "Search"
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid "In the following window, enter the server's hostname (or IP) where your Plex media resides. Mind the case!"
msgstr ""
# For setting up direct paths and adding network credentials - input window for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid "In the following window, enter the network protocol you would like to use. This is likely 'smb'."
msgstr ""
# For setting up direct paths and adding network credentials - input window protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr ""
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -385,6 +453,10 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr ""
msgctxt "#30417"
msgid "Could not change the Kodi settings file {0}. PKC might not work correctly. Error: {1}"
msgstr ""
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -395,9 +467,14 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr ""
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr ""
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr ""
# PKC Settings, category name
@ -439,6 +516,11 @@ msgctxt "#30513"
msgid "Limit artwork cache threads (recommended for rpi)"
msgstr ""
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr ""
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -451,7 +533,7 @@ msgstr ""
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr ""
# PKC Settings - Playback
@ -479,6 +561,21 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr ""
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr ""
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr ""
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -541,7 +638,7 @@ msgstr ""
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
@ -549,6 +646,11 @@ msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr ""
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr ""
msgctxt "#30544"
msgid "Artwork"
msgstr ""
@ -558,6 +660,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr ""
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr ""
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr ""
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr ""
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -578,13 +695,27 @@ msgctxt "#33003"
msgid "Server is online"
msgstr ""
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr ""
# Plex notification when we need to use direct streaming (instead of transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr ""
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr ""
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgid "User is unauthorized for server {0}"
msgstr ""
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr ""
# Dialog before playback
@ -709,7 +840,7 @@ msgstr ""
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr ""
# Appended to a listed PMS if it is in the same LAN network as PKC
@ -767,6 +898,16 @@ msgctxt "#39035"
msgid "Replace Plex paths /volume1/media or \\\\myserver\\media with custom SMB paths smb://NAS/mystuff"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -834,7 +975,7 @@ msgstr ""
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr ""
# PKC Settings - Sync
@ -864,7 +1005,7 @@ msgstr ""
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgid "Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
# PKC Settings, category name
@ -872,11 +1013,6 @@ msgctxt "#39057"
msgid "Customize Paths"
msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -914,7 +1050,7 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid "Recently Added: Also show already watched movies (Refresh Plex playlist/nodes!)"
msgid "Recently Added: Also show already watched movies"
msgstr ""
# PKC Settings - Connection
@ -924,7 +1060,7 @@ msgstr ""
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr ""
# PKC Settings - Connection
@ -942,6 +1078,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr ""
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -952,11 +1093,6 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr ""
# PKC Settings - Playback
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid "If you use several Plex libraries of one kind, e.g. \"Kids Movies\" and \"Parents Movies\", be sure to check the Wiki: https://goo.gl/JFtQV9"
@ -964,7 +1100,7 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgid "Maximum number of videos to show in widgets"
msgstr ""
# PKC Settings - Plex
@ -977,6 +1113,62 @@ msgctxt "#39079"
msgid "Plex Companion could not open the GDM port. Please change it in the PKC settings."
msgstr ""
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are identical!
msgctxt "#39080"
msgid "Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr ""
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr ""
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr ""
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr ""
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr ""
@ -985,10 +1177,6 @@ msgctxt "#39201"
msgid "Settings"
msgstr ""
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr ""
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr ""
@ -1012,7 +1200,7 @@ msgstr ""
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr ""
msgctxt "#39210"
@ -1023,9 +1211,9 @@ msgctxt "#39211"
msgid "Watch later"
msgstr ""
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgid "{0} offline"
msgstr ""
msgctxt "#39215"
@ -1033,7 +1221,7 @@ msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr ""
msgctxt "#39217"
msgid "Does your Plex Media Server support SSL connections? (https instead of http)?"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr ""
msgctxt "#39218"
@ -1054,7 +1242,7 @@ msgid "plex.tv toggle successful"
msgstr ""
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr ""
msgctxt "#39223"
@ -1079,9 +1267,24 @@ msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr ""
# Message in the PKC settings to display the plex.tv username. Leave the colon :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgid "Plex admin user"
msgstr ""
# Error message if user could not log in; the actual user name will be appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr ""
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr ""
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr ""
msgctxt "#39250"
@ -1141,7 +1344,7 @@ msgid " may not work correctly until the database is reset."
msgstr ""
msgctxt "#39403"
msgid "Cancelling the database syncing process. Current Kodi version is unsupported. Please verify your logs for more info."
msgid "The current Kodi version is not supported by PKC. Please consult the Plex forum."
msgstr ""
msgctxt "#39405"
@ -1152,10 +1355,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr ""
msgctxt "#39407"
msgid "Full library sync finished"
msgstr ""
msgctxt "#39408"
msgid "Sync had to skip some items because they could not be processed. Kodi may be instable now!! Please post your Kodi logs to the Plex forum."
msgstr ""
@ -1176,6 +1375,10 @@ msgctxt "#39501"
msgid "Collections"
msgstr ""
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600"
msgid "Are you sure you want to reset your local Kodi database? A re-sync of the Plex data will take time afterwards."
msgstr ""
@ -1219,9 +1422,9 @@ msgid "Use at your own risk"
msgstr ""
# If user gets prompted to choose between several subtitles. Leave the number one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgid "Don't burn-in any subtitle"
msgstr ""
# If user gets prompted to choose between several audio/subtitle tracks and language is unknown
@ -1266,7 +1469,7 @@ msgstr ""
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgid "Synching playlists"
msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1,13 +1,14 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2017
# Croneter None <croneter@gmail.com>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: PlexKodiConnect\n"
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2017\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2020\n"
"Language-Team: Spanish (Argentina) (https://www.transifex.com/croneter/teams/73837/es_AR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -26,7 +27,7 @@ msgstr "Dirección del servidor (IP)"
msgctxt "#30001"
msgid "Searching for PMS"
msgstr ""
msgstr "Buscando el PMS"
msgctxt "#30002"
msgid "Preferred playback method"
@ -40,6 +41,15 @@ msgid ""
"Warning: Kodi setting \"Play next video automatically\" is enabled. This "
"could break PKC. Deactivate?"
msgstr ""
"Advertencia: El ajuste de Kodi \"Reproducir el siguiente video "
"automáticamente\" está activado. Esto puede dañar PKC. ¿Desactivar?"
msgctxt "#30004"
msgid ""
"The Kodi webserver is needed for artwork caching. PKC already set a strong, "
"random password automatically if you haven't done so already. Please confirm"
" the next dialog that you want to enable the webserver now with Yes."
msgstr ""
msgctxt "#30005"
msgid "Username: "
@ -48,32 +58,33 @@ msgstr "Usuario: "
# Sync notification displayed if there is still artwork to be cached to Kodi
msgctxt "#30006"
msgid "Caching %s Plex images"
msgstr ""
msgstr "Hachiendo caché en %s imágenes de Plex"
# Sync notification displayed if syncing of major artwork is done
msgctxt "#30007"
msgid "Plex image caching done"
msgstr ""
msgstr "El caché de imágenes de Plex fue completado"
# PKC settings artwork: Enable notifications for artwork image sync
msgctxt "#30008"
msgid "Enable notifications for image caching"
msgstr ""
msgstr "Activar notificaciones para caché de imágenes"
# PKC settings artwork: Enable image caching during Kodi playback
msgctxt "#30009"
msgid "Enable image caching during Kodi playback (restart Kodi!)"
msgstr ""
"Activar caché de imágenes durante la reproducción en Kodi (reinicie Kodi)"
# PKC settings - Artwork
msgctxt "#30010"
msgid "Approximate progress"
msgstr ""
msgstr "Progreso aproximado"
# PKC settings - Artwork
msgctxt "#30011"
msgid "Plex artwork (posters and backgrounds) left to cache:"
msgstr ""
msgid "Artwork left to cache:"
msgstr "Arte pendiente de caché:"
# Button text
msgctxt "#30012"
@ -89,10 +100,10 @@ msgctxt "#30014"
msgid "Connection"
msgstr "Conexión"
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgstr ""
msgid "Fanart download already running"
msgstr "La descarga de fondos ya esta en marcha"
msgctxt "#30016"
msgid "Device Name"
@ -106,18 +117,33 @@ msgstr "No autorizado para PMS"
# Sync notification displayed for the number of fanart.tv lookups left
msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr ""
msgstr "Verificando en Fanart.tv %s ítems"
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr ""
msgstr "Búsqueda en fanart.tv completada"
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr "Sincronizar listas de reproducción de Plex (reiniciar Kodi!)"
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr "Sólo sincronizar listas de reproducción específicas de Plex con Kodi"
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr "Avanzado"
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr "Sólo sincronizar listas de reproducción específicas de Kodi con Plex"
msgctxt "#30024"
msgid "Username"
msgstr "Usuario"
@ -126,6 +152,33 @@ msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr "Mostrar mensaje si el PMS está desconectado"
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr ""
"Prefijo en el nombre de la lista de reproducción en Plex para designer la "
"sincronización"
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr ""
"Prefijo en el nombre de la lista de reproducción de Kodi para designer la "
"sincronización"
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "El caché de imágenes solo-PKC fue completado"
# Warning shown when PKC switches to the Kodi default skin Estuary
msgctxt "#30029"
msgid ""
"To ensure a smooth PlexKodiConnect experience, it is HIGHLY recommended to "
"use Kodi's default skin \"Estuary\" for initial set-up and for possible "
"database resets. Continue?"
msgstr ""
msgctxt "#30030"
msgid "Port Number"
msgstr "Número de puerto"
@ -225,6 +278,10 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr "Calidad de vídeo si es necesario Transcodificar"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
msgctxt "#30165"
msgid "Direct Play"
msgstr "Reproducción Directa"
@ -281,6 +338,46 @@ msgctxt "#30198"
msgid "Search"
msgstr "Buscar"
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid ""
"In the following window, enter the server's hostname (or IP) where your Plex"
" media resides. Mind the case!"
msgstr ""
"En la siguiente Ventana, digite el nombre de host del servidor (o dirección "
"IP) donde reside su media de Plex. ¡Tenga cuidado con mayúsculas!"
# For setting up direct paths and adding network credentials - input window
# for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr "Digite el nombre de host del servidor (o dirección IP)"
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid ""
"In the following window, enter the network protocol you would like to use. "
"This is likely 'smb'."
msgstr ""
"En la siguiente Ventana, digite el protocolo de red que desea utilizer. Esto"
" es probablemente 'smb'."
# For setting up direct paths and adding network credentials - input window
# protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr "Digite protocolo de ted"
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr "El nombre de host o dirección IP '{0}' que usted digitó no es válido."
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr "El protocolo '{0}' que usted digitó no está soportado."
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -394,6 +491,14 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr "Configuración para el servidor Plex"
msgctxt "#30417"
msgid ""
"Could not change the Kodi settings file {0}. PKC might not work correctly. "
"Error: {1}"
msgstr ""
"No se ha podido cambiar el archivo de ajustes de Kodi {0}. PKC puede no "
"funcionar correctamente. Error: {1}"
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -404,10 +509,16 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr "Certificado SSL de cliente"
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
msgstr "[COLOR yellow] Restablecer intentos de inicio de sesión[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr "Sincronizar el arte de Plex desde PMS (recomendado)"
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr ""
"Fallo al validar el certificado SSL. Consulte {0} para ver soluciones."
# PKC Settings, category name
msgctxt "#30506"
@ -441,13 +552,20 @@ msgstr "Modo de reproducción"
# PKC Settings - Artwork
msgctxt "#30512"
msgid "Cache all artwork for a smooth Kodi experience"
msgstr ""
msgstr "Hacer caché de todo el arte para una experiencia óptima en Kodi"
# PKC Settings - Artwork
msgctxt "#30513"
msgid "Limit artwork cache threads (recommended for rpi)"
msgstr "Limitar hilos de caché de arte (recomendado para rpi)"
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr ""
"Mostrar todos los extras de Plex en vez de reproducir inmediatamente los "
"trailers"
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -460,8 +578,8 @@ msgstr "Reproducción"
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgstr "[COLOR yellow] Digitar credenciales de red[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr "Establezca credenciales de red para Direct Paths y direct play"
# PKC Settings - Playback
msgctxt "#30518"
@ -476,7 +594,7 @@ msgstr "Preguntar si reproducir tráilers"
# PKC Settings - Plex
msgctxt "#30520"
msgid "Skip PMS delete confirmation (use at your own risk)"
msgstr ""
msgstr "Omitir la confirmación de borrado PMS (utilice a su propio riesgo)"
# PKC Settings - Playback
msgctxt "#30521"
@ -488,6 +606,23 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "Obligar transcodificación de h265/HEVC"
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr ""
"Mostrar tambien el progreso de sincronizacion para el estado de reproduccion"
" y datos del usuario"
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Seleccionar librerias de Plex para sincronizar"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -522,6 +657,7 @@ msgstr "Mensajes del servidor"
msgctxt "#30535"
msgid "Generate a new unique Plex device Id (e.g. to clone Kodi)"
msgstr ""
"Generar un nuevo id de dispositivo único de Plex (ej. para clonar Kodi)"
# PKC Settings - Connection
msgctxt "#30536"
@ -537,6 +673,8 @@ msgstr "REINICIAR KODI SI HACE CAMBIOS"
msgctxt "#30538"
msgid "Manual complete reset of Kodi database necessary, see \"Advanced\""
msgstr ""
"Se require un restablecimiento manual complete de la base de datos de Kodi, "
"vea \"Avanzado\""
# PKC Settings - Artwork
msgctxt "#30539"
@ -550,14 +688,19 @@ msgstr "Descargar arte de sagas de FanArtTV"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "No solicitar elegir un stream o una calidad en particular"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr "Escoger siempre la mejor calidad para tráilers"
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr "Preferir arte de Kodi para colecciones/sagas"
msgctxt "#30544"
msgid "Artwork"
msgstr "Arte"
@ -567,6 +710,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Obligar transcodificar fotografías"
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr ""
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr ""
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr ""
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -587,15 +745,29 @@ msgctxt "#33003"
msgid "Server is online"
msgstr "Servidor está en línea"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr ""
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr ""
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "Nombre de usuario o contraseña incorrecto"
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgstr ""
"No se pudo autenticar demasiadas veces. Restablecer en la configuración."
msgid "User is unauthorized for server {0}"
msgstr "El usuario no esta autorizado para el servidor {0}"
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr "Plex.tv no proveyó una lista válida de usuarios de Plex, disculpas."
# Dialog before playback
msgctxt "#33013"
@ -652,7 +824,7 @@ msgstr "Tamaño de subtítulos incrustados"
# PKC Settings - Sync
msgctxt "#39003"
msgid "Number of simultaneous download threads"
msgstr ""
msgstr "Cantidad de hilos simultáneos de descarga"
# PKC Settings - Plex
msgctxt "#39004"
@ -728,18 +900,20 @@ msgstr ""
msgctxt "#39018"
msgid "Repair the Kodi database (force update all content)"
msgstr ""
"Reparar la base de datos de Kodi (actualización forzosa de todo el "
"contenido)"
# PKC Settings - Advanced
msgctxt "#39019"
msgid "Reset the Kodi database and optionally reset PlexKodiConnect"
msgstr ""
"Restablecer la base de datos de Kodi y oopcionalmente restablecer "
"PlexKodiConnect"
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgstr ""
"[COLOR yellow] Hacer caché ahora a todas las imágenes al caché de texturas "
"de Kodi[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr "Hacer caché ahora de todas las imágenes al Kodi texture cache"
# Appended to a listed PMS if it is in the same LAN network as PKC
msgctxt "#39022"
@ -796,6 +970,8 @@ msgid ""
"Kodi cannot locate the file %s. Please verify your PKC settings. Stop "
"syncing?"
msgstr ""
"Kodi no puede localizer el archivo %s. Por favor verificar sus ajustes de "
"PKC. ¿Detener la sincronización?"
# Pop-up on initial sync
msgctxt "#39033"
@ -820,6 +996,16 @@ msgstr ""
"Reemplazar rutas Plex /volume1/media o \\\\myserver\\media con rutas SMB "
"personalizadas smb://NAS/mystuff"
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr "Escapar caracteres especiales en la ruta (p. ej. espacio a %20)"
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr "Caracteres seguros para urls http(s), dav(s) y (s)ftp"
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -896,8 +1082,8 @@ msgstr "¿Nada funciona? ¡Intente un reset completo!"
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgstr "[COLOR yellow] Elegir servidor Plex de una lista[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr "Elegir el Servidor Plex de una lista"
# PKC Settings - Sync
msgctxt "#39051"
@ -926,19 +1112,15 @@ msgstr "Buscando servidor Plex"
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgstr "Utilizado por la Sincronización al intentar Reproducción Directa"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
# PKC Settings, category name
msgctxt "#39057"
msgid "Customize Paths"
msgstr "Personalizar rutas"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Extender vista de series \"On Deck\" de Plex a todoas las series"
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -975,16 +1157,12 @@ msgstr "Añadidos Recientemente: Además mostrar episodios ya vistos"
# PKC Settings - Appearance Tweaks
msgctxt "#39065"
msgid "Force-refresh Kodi skin on stopping playback"
msgstr ""
msgstr "Refrescar el skin de Kodi al detener la reproducción"
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr ""
"Añadidos Recientemente: También mostrar películas ya vistas (¡Actualize las "
"listas de reproducción/nodos Plex!)"
msgid "Recently Added: Also show already watched movies"
msgstr "Recién Añadido: Mostrar tambien películas ya vistas"
# PKC Settings - Connection
msgctxt "#39067"
@ -993,10 +1171,8 @@ msgstr "El Plex Media Server actual:"
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgstr ""
"[COLOR yellow] Introducir manualmente la dirección del Plex Media "
"Server[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr "Digitar manualmente la dirección del Plex Media Server"
# PKC Settings - Connection
msgctxt "#39069"
@ -1013,6 +1189,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Estado actual de plex.tv:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1023,10 +1204,10 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Series"
# PKC Settings - Playback
# PKC Settings - Sync
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgstr "Siempre use subtítulos de Plex por defecto si es posible"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
@ -1040,9 +1221,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgstr ""
"Número de elementos del PMS a mostrar en widgets (por ejemplo \"On Deck\")"
msgid "Maximum number of videos to show in widgets"
msgstr "Número máximo de videos a mostrar en los widgets"
# PKC Settings - Plex
msgctxt "#39078"
@ -1059,6 +1239,68 @@ msgstr ""
"Plex Companion no pudo abrir el puerto GDM. Por favor cámbielo en los "
"ajustes de PKC."
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
# identical!
msgctxt "#39080"
msgid ""
"Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if "
"you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
"¿Utilizar Add-on Paths (por defecto, fácil) o Direct Paths? Escoja Add-on "
"Paths si no está seguro(a). ¡PKC no funcionará si su configuración de "
"Direct Paths está incorrecta!"
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr "Add-on Paths"
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr "Direct Paths"
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr "Introduzca la URL o IP del PMS"
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr "Introduzca el puerto del PMS"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr "Recargar Kodi para aplicar todos los ajustes."
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr ""
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr "Terminar sesión del usuario de Plex Home "
@ -1067,10 +1309,6 @@ msgctxt "#39201"
msgid "Settings"
msgstr "Opciones"
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr "Actualizar listas de reproducción/nodos de Plex"
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr "Realizar sincronización manual de la bibilioteca"
@ -1100,10 +1338,8 @@ msgstr "No se pudo restablecer PKC. Trate de reiniciar Kodi."
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgstr ""
"[COLOR yellow] Alterne Inicio de sesión de plex.tv (ingresar o "
"salir)[/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr "Alternar login de plex.tv (entrar o salir)"
msgctxt "#39210"
msgid "Not yet connected to Plex Server"
@ -1113,21 +1349,19 @@ msgctxt "#39211"
msgid "Watch later"
msgstr "Ver Luego"
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by
# e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgstr "está desconectado"
msgid "{0} offline"
msgstr "{0} fuera de linea"
msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "Escriba el IP o URL de su Plex Media Server, por ejemplo:"
msgctxt "#39217"
msgid ""
"Does your Plex Media Server support SSL connections? (https instead of "
"http)?"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr ""
"¿Su Plex Media Server admite conexiones SSL? ¿(https en lugar de http)?"
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1147,8 +1381,8 @@ msgid "plex.tv toggle successful"
msgstr "alternado de plex.tv exitoso"
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgstr "[COLOR yellow Buscar ahora fanart faltante en FanartTV[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr "Buscar fanart faltante en fanart.tv ahora"
msgctxt "#39223"
msgid ""
@ -1176,11 +1410,26 @@ msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr "Conectado a plex.tv"
# Message in the PKC settings to display the plex.tv username. Leave the colon
# :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgstr "Usuario de Plex:"
msgid "Plex admin user"
msgstr "Administrador de Plex"
# Error message if user could not log in; the actual user name will be
# appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr "Inicio de session con plex.tv falló para el usuario"
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr "Usuario de Plex home registrado"
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr "Cambiar usuario de Plex"
msgctxt "#39250"
msgid ""
@ -1253,12 +1502,11 @@ msgstr ""
msgctxt "#39403"
msgid ""
"Cancelling the database syncing process. Current Kodi version is "
"unsupported. Please verify your logs for more info."
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
"Cancelando proceso de sincronización de la base de datos. La versión de Kodi"
" actual no es compatible. Por favor, compruebe la bitácora para obtener más "
"información."
"La versión actual de Kodi no está soportada por PKC. Por favor consultar el"
" fórum de Plex."
msgctxt "#39405"
msgid "Plex playlists/nodes refreshed"
@ -1268,10 +1516,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr "Error de actualización de listas de reproducción/nodos de Plex"
msgctxt "#39407"
msgid "Full library sync finished"
msgstr "Sincronización completa de la biblioteca terminada"
msgctxt "#39408"
msgid ""
"Sync had to skip some items because they could not be processed. Kodi may be"
@ -1303,6 +1547,10 @@ msgctxt "#39501"
msgid "Collections"
msgstr "Sagas"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr "On Deck de PKC (más rápido)"
msgctxt "#39600"
msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1361,11 +1609,10 @@ msgctxt "#39705"
msgid "Use at your own risk"
msgstr "Usar a su propio riesgo"
# If user gets prompted to choose between several subtitles. Leave the number
# one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgstr "1 Sin subtitulos"
msgid "Don't burn-in any subtitle"
msgstr ""
# If user gets prompted to choose between several audio/subtitle tracks and
# language is unknown
@ -1419,8 +1666,8 @@ msgstr "Sincronizar"
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgstr "objetos"
msgid "Synching playlists"
msgstr "Sincronizando listas"
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}!
@ -1444,8 +1691,8 @@ msgid ""
"Do you want to replace your custom user ratings with an indicator of how "
"many versions of a media item you posses?"
msgstr ""
"¿Quiere reemplazar su valoración personalizada con cuántas versione posee de"
" un elemento de medios?"
"¿Quiere reemplazar su valoración personalizada por cuántas versiones posee "
"de un elemento de medios?"
# In PKC Settings under Sync
msgctxt "#39719"

View file

@ -1,13 +1,16 @@
# XBMC Media Center language file
# Translators:
# Bartolome Soriano <bsoriano@gmail.com>, 2017
# Dani <danichispa@gmail.com>, 2019
# Bartolome Soriano <bsoriano@gmail.com>, 2019
# Croneter None <croneter@gmail.com>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: PlexKodiConnect\n"
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Bartolome Soriano <bsoriano@gmail.com>, 2017\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2020\n"
"Language-Team: Spanish (Spain) (https://www.transifex.com/croneter/teams/73837/es_ES/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -26,7 +29,7 @@ msgstr "Dirección del servidor (IP)"
msgctxt "#30001"
msgid "Searching for PMS"
msgstr ""
msgstr "Buscando el PMS"
msgctxt "#30002"
msgid "Preferred playback method"
@ -40,6 +43,15 @@ msgid ""
"Warning: Kodi setting \"Play next video automatically\" is enabled. This "
"could break PKC. Deactivate?"
msgstr ""
"Advertencia: El ajuste de Kodi \"Reproducir el siguiente video "
"automáticamente\" está activado. Esto puede dañar PKC. ¿Desactivar?"
msgctxt "#30004"
msgid ""
"The Kodi webserver is needed for artwork caching. PKC already set a strong, "
"random password automatically if you haven't done so already. Please confirm"
" the next dialog that you want to enable the webserver now with Yes."
msgstr ""
msgctxt "#30005"
msgid "Username: "
@ -48,32 +60,33 @@ msgstr "Usuario: "
# Sync notification displayed if there is still artwork to be cached to Kodi
msgctxt "#30006"
msgid "Caching %s Plex images"
msgstr ""
msgstr "Hachiendo caché en %s imágenes de Plex"
# Sync notification displayed if syncing of major artwork is done
msgctxt "#30007"
msgid "Plex image caching done"
msgstr ""
msgstr "El caché de imágenes de Plex fue completado"
# PKC settings artwork: Enable notifications for artwork image sync
msgctxt "#30008"
msgid "Enable notifications for image caching"
msgstr ""
msgstr "Activar notificaciones para caché de imágenes"
# PKC settings artwork: Enable image caching during Kodi playback
msgctxt "#30009"
msgid "Enable image caching during Kodi playback (restart Kodi!)"
msgstr ""
"Activar caché de imágenes durante la reproducción en Kodi (reinicie Kodi)"
# PKC settings - Artwork
msgctxt "#30010"
msgid "Approximate progress"
msgstr ""
msgstr "Progreso aproximado"
# PKC settings - Artwork
msgctxt "#30011"
msgid "Plex artwork (posters and backgrounds) left to cache:"
msgstr ""
msgid "Artwork left to cache:"
msgstr "Arte pendiente de caché:"
# Button text
msgctxt "#30012"
@ -89,10 +102,10 @@ msgctxt "#30014"
msgid "Connection"
msgstr "Conexión"
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgstr ""
msgid "Fanart download already running"
msgstr "La descarga de fondos ya esta en marcha"
msgctxt "#30016"
msgid "Device Name"
@ -106,18 +119,33 @@ msgstr "No autorizado para PMS"
# Sync notification displayed for the number of fanart.tv lookups left
msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr ""
msgstr "Verificando en Fanart.tv %s ítems"
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr ""
msgstr "Búsqueda en fanart.tv completada"
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr "Sincronizar listas de reproducción de Plex (reiniciar Kodi!)"
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr "Sólo sincronizar listas de reproducción específicas de Plex con Kodi"
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr "Avanzado"
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr "Sólo sincronizar listas de reproducción específicas de Kodi con Plex"
msgctxt "#30024"
msgid "Username"
msgstr "Usuario"
@ -126,6 +154,33 @@ msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr "Mostrar mensaje si el PMS está desconectado"
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr ""
"Prefijo en el nombre de la lista de reproducción en Plex para designer la "
"sincronización"
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr ""
"Prefijo en el nombre de la lista de reproducción de Kodi para designer la "
"sincronización"
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "El caché de imágenes solo-PKC fue completado"
# Warning shown when PKC switches to the Kodi default skin Estuary
msgctxt "#30029"
msgid ""
"To ensure a smooth PlexKodiConnect experience, it is HIGHLY recommended to "
"use Kodi's default skin \"Estuary\" for initial set-up and for possible "
"database resets. Continue?"
msgstr ""
msgctxt "#30030"
msgid "Port Number"
msgstr "Número de puerto"
@ -225,6 +280,10 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr "Calidad de vídeo si es necesario Transcodificar"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
msgctxt "#30165"
msgid "Direct Play"
msgstr "Reproducción Directa"
@ -281,6 +340,46 @@ msgctxt "#30198"
msgid "Search"
msgstr "Buscar"
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid ""
"In the following window, enter the server's hostname (or IP) where your Plex"
" media resides. Mind the case!"
msgstr ""
"En la siguiente Ventana, digite el nombre de host del servidor (o dirección "
"IP) donde reside su media de Plex. ¡Tenga cuidado con mayúsculas!"
# For setting up direct paths and adding network credentials - input window
# for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr "Digite el nombre de host del servidor (o dirección IP)"
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid ""
"In the following window, enter the network protocol you would like to use. "
"This is likely 'smb'."
msgstr ""
"En la siguiente Ventana, digite el protocolo de red que desea utilizer. Esto"
" es probablemente 'smb'."
# For setting up direct paths and adding network credentials - input window
# protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr "Digite protocolo de ted"
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr "El nombre de host o dirección IP '{0}' que usted digitó no es válido."
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr "El protocolo '{0}' que usted digitó no está soportado."
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -394,6 +493,14 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr "Configuración para el servidor Plex"
msgctxt "#30417"
msgid ""
"Could not change the Kodi settings file {0}. PKC might not work correctly. "
"Error: {1}"
msgstr ""
"No se ha podido cambiar el archivo de ajustes de Kodi {0}. PKC puede no "
"funcionar correctamente. Error: {1}"
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -404,10 +511,16 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr "Certificado SSL de cliente"
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
msgstr "[COLOR yellow] Restablecer intentos de inicio de sesión[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr "Sincronizar el arte de Plex desde PMS (recomendado)"
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr ""
"Fallo al validar el certificado SSL. Consulte {0} para ver soluciones."
# PKC Settings, category name
msgctxt "#30506"
@ -441,13 +554,20 @@ msgstr "Modo de reproducción"
# PKC Settings - Artwork
msgctxt "#30512"
msgid "Cache all artwork for a smooth Kodi experience"
msgstr ""
msgstr "Hacer caché de todo el arte para una experiencia óptima en Kodi"
# PKC Settings - Artwork
msgctxt "#30513"
msgid "Limit artwork cache threads (recommended for rpi)"
msgstr "Limitar hilos de caché de arte (recomendado para rpi)"
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr ""
"Mostrar todos los extras de Plex en vez de reproducir inmediatamente los "
"trailers"
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -460,8 +580,8 @@ msgstr "Reproducción"
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgstr "[COLOR yellow] Digitar credenciales de red[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr "Establezca credenciales de red para Direct Paths y direct play"
# PKC Settings - Playback
msgctxt "#30518"
@ -476,7 +596,7 @@ msgstr "Preguntar si reproducir tráilers"
# PKC Settings - Plex
msgctxt "#30520"
msgid "Skip PMS delete confirmation (use at your own risk)"
msgstr ""
msgstr "Omitir la confirmación de borrado PMS (utilice a su propio riesgo)"
# PKC Settings - Playback
msgctxt "#30521"
@ -488,6 +608,23 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "Obligar transcodificación de h265/HEVC"
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr ""
"Mostrar tambien el progreso de sincronizacion para el estado de reproduccion"
" y datos del usuario"
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Seleccionar librerias de Plex para sincronizar"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -522,6 +659,7 @@ msgstr "Mensajes del servidor"
msgctxt "#30535"
msgid "Generate a new unique Plex device Id (e.g. to clone Kodi)"
msgstr ""
"Generar un nuevo id de dispositivo único de Plex (ej. para clonar Kodi)"
# PKC Settings - Connection
msgctxt "#30536"
@ -537,6 +675,8 @@ msgstr "REINICIAR KODI SI HACE CAMBIOS"
msgctxt "#30538"
msgid "Manual complete reset of Kodi database necessary, see \"Advanced\""
msgstr ""
"Se require un restablecimiento manual complete de la base de datos de Kodi, "
"vea \"Avanzado\""
# PKC Settings - Artwork
msgctxt "#30539"
@ -550,14 +690,19 @@ msgstr "Descargar arte de sagas de FanArtTV"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "No solicitar elegir un stream o una calidad en particular"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr "Escoger siempre la mejor calidad para tráilers"
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr "Preferir arte de Kodi para colecciones/sagas"
msgctxt "#30544"
msgid "Artwork"
msgstr "Arte"
@ -567,6 +712,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Obligar transcodificar fotografías"
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr ""
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr ""
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr ""
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -587,15 +747,29 @@ msgctxt "#33003"
msgid "Server is online"
msgstr "Servidor está en línea"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr ""
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr ""
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "Nombre de usuario o contraseña incorrecto"
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgstr ""
"No se pudo autenticar demasiadas veces. Restablecer en la configuración."
msgid "User is unauthorized for server {0}"
msgstr "El usuario no esta autorizado para el servidor {0}"
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr "Plex.tv no proveyó una lista válida de usuarios de Plex, disculpas."
# Dialog before playback
msgctxt "#33013"
@ -652,7 +826,7 @@ msgstr "Tamaño de subtítulos incrustados"
# PKC Settings - Sync
msgctxt "#39003"
msgid "Number of simultaneous download threads"
msgstr ""
msgstr "Cantidad de hilos simultáneos de descarga"
# PKC Settings - Plex
msgctxt "#39004"
@ -728,18 +902,20 @@ msgstr ""
msgctxt "#39018"
msgid "Repair the Kodi database (force update all content)"
msgstr ""
"Reparar la base de datos de Kodi (actualización forzosa de todo el "
"contenido)"
# PKC Settings - Advanced
msgctxt "#39019"
msgid "Reset the Kodi database and optionally reset PlexKodiConnect"
msgstr ""
"Restablecer la base de datos de Kodi y oopcionalmente restablecer "
"PlexKodiConnect"
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgstr ""
"[COLOR yellow] Hacer caché ahora a todas las imágenes al caché de texturas "
"de Kodi[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr "Hacer caché ahora de todas las imágenes al Kodi texture cache"
# Appended to a listed PMS if it is in the same LAN network as PKC
msgctxt "#39022"
@ -796,6 +972,8 @@ msgid ""
"Kodi cannot locate the file %s. Please verify your PKC settings. Stop "
"syncing?"
msgstr ""
"Kodi no puede localizer el archivo %s. Por favor verificar sus ajustes de "
"PKC. ¿Detener la sincronización?"
# Pop-up on initial sync
msgctxt "#39033"
@ -820,6 +998,16 @@ msgstr ""
"Reemplazar rutas Plex /volume1/media o \\\\myserver\\media con rutas SMB "
"personalizadas smb://NAS/mystuff"
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr "Escapar caracteres especiales en la ruta (p. ej. espacio a %20)"
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr "Caracteres seguros para urls http(s), dav(s) y (s)ftp"
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -896,8 +1084,8 @@ msgstr "¿Nada funciona? ¡Intente un reset completo!"
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgstr "[COLOR yellow] Elegir servidor Plex de una lista[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr "Elegir el Servidor Plex de una lista"
# PKC Settings - Sync
msgctxt "#39051"
@ -926,19 +1114,15 @@ msgstr "Buscando servidor Plex"
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgstr "Utilizado por la Sincronización al intentar Reproducción Directa"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
# PKC Settings, category name
msgctxt "#39057"
msgid "Customize Paths"
msgstr "Personalizar rutas"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Extender vista de series \"On Deck\" de Plex a todoas las series"
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -975,16 +1159,12 @@ msgstr "Añadidos Recientemente: Además mostrar episodios ya vistos"
# PKC Settings - Appearance Tweaks
msgctxt "#39065"
msgid "Force-refresh Kodi skin on stopping playback"
msgstr ""
msgstr "Refrescar el skin de Kodi al detener la reproducción"
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr ""
"Añadidos Recientemente: También mostrar películas ya vistas (¡Actualize las "
"listas de reproducción/nodos Plex!)"
msgid "Recently Added: Also show already watched movies"
msgstr "Recién Añadido: Mostrar tambien películas ya vistas"
# PKC Settings - Connection
msgctxt "#39067"
@ -993,10 +1173,8 @@ msgstr "El Plex Media Server actual:"
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgstr ""
"[COLOR yellow] Introducir manualmente la dirección del Plex Media "
"Server[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr "Digitar manualmente la dirección del Plex Media Server"
# PKC Settings - Connection
msgctxt "#39069"
@ -1013,6 +1191,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Estado actual de plex.tv:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1023,10 +1206,10 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Series"
# PKC Settings - Playback
# PKC Settings - Sync
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgstr "Siempre use subtítulos de Plex por defecto si es posible"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
@ -1040,9 +1223,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgstr ""
"Número de elementos del PMS a mostrar en widgets (por ejemplo \"On Deck\")"
msgid "Maximum number of videos to show in widgets"
msgstr "Número máximo de videos a mostrar en los widgets"
# PKC Settings - Plex
msgctxt "#39078"
@ -1059,6 +1241,68 @@ msgstr ""
"Plex Companion no pudo abrir el puerto GDM. Por favor cámbielo en los "
"ajustes de PKC."
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
# identical!
msgctxt "#39080"
msgid ""
"Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if "
"you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
"¿Utilizar Add-on Paths (por defecto, fácil) o Direct Paths? Escoja Add-on "
"Paths si no está seguro(a). ¡PKC no funcionará si su configuración de "
"Direct Paths está incorrecta!"
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr "Add-on Paths"
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr "Direct Paths"
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr "Introduzca la URL o IP del PMS"
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr "Introduzca el puerto del PMS"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr "Recargar Kodi para aplicar todos los ajustes."
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr ""
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr "Terminar sesión del usuario de Plex Home "
@ -1067,10 +1311,6 @@ msgctxt "#39201"
msgid "Settings"
msgstr "Opciones"
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr "Actualizar listas de reproducción/nodos de Plex"
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr "Realizar sincronización manual de la bibilioteca"
@ -1100,10 +1340,8 @@ msgstr "No se pudo restablecer PKC. Trate de reiniciar Kodi."
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgstr ""
"[COLOR yellow] Alterne Inicio de sesión de plex.tv (ingresar o "
"salir)[/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr "Alternar login de plex.tv (entrar o salir)"
msgctxt "#39210"
msgid "Not yet connected to Plex Server"
@ -1113,21 +1351,19 @@ msgctxt "#39211"
msgid "Watch later"
msgstr "Ver Luego"
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by
# e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgstr "está desconectado"
msgid "{0} offline"
msgstr "{0} fuera de linea"
msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "Escriba el IP o URL de su Plex Media Server, por ejemplo:"
msgctxt "#39217"
msgid ""
"Does your Plex Media Server support SSL connections? (https instead of "
"http)?"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr ""
"¿Su Plex Media Server admite conexiones SSL? ¿(https en lugar de http)?"
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1147,8 +1383,8 @@ msgid "plex.tv toggle successful"
msgstr "alternado de plex.tv exitoso"
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgstr "[COLOR yellow Buscar ahora fanart faltante en FanartTV[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr "Buscar fanart faltante en fanart.tv ahora"
msgctxt "#39223"
msgid ""
@ -1176,11 +1412,26 @@ msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr "Conectado a plex.tv"
# Message in the PKC settings to display the plex.tv username. Leave the colon
# :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgstr "Usuario de Plex:"
msgid "Plex admin user"
msgstr "Administrador de Plex"
# Error message if user could not log in; the actual user name will be
# appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr "Inicio de session con plex.tv falló para el usuario"
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr "Usuario de Plex home registrado"
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr "Cambiar usuario de Plex"
msgctxt "#39250"
msgid ""
@ -1253,12 +1504,11 @@ msgstr ""
msgctxt "#39403"
msgid ""
"Cancelling the database syncing process. Current Kodi version is "
"unsupported. Please verify your logs for more info."
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
"Cancelando proceso de sincronización de la base de datos. La versión de Kodi"
" actual no es compatible. Por favor, compruebe la bitácora para obtener más "
"información."
"La versión actual de Kodi no está soportada por PKC. Por favor consultar el"
" fórum de Plex."
msgctxt "#39405"
msgid "Plex playlists/nodes refreshed"
@ -1268,10 +1518,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr "Error de actualización de listas de reproducción/nodos de Plex"
msgctxt "#39407"
msgid "Full library sync finished"
msgstr "Sincronización completa de la biblioteca terminada"
msgctxt "#39408"
msgid ""
"Sync had to skip some items because they could not be processed. Kodi may be"
@ -1303,6 +1549,10 @@ msgctxt "#39501"
msgid "Collections"
msgstr "Sagas"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr "On Deck de PKC (más rápido)"
msgctxt "#39600"
msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1361,11 +1611,10 @@ msgctxt "#39705"
msgid "Use at your own risk"
msgstr "Usar a su propio riesgo"
# If user gets prompted to choose between several subtitles. Leave the number
# one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgstr "1 Sin subtitulos"
msgid "Don't burn-in any subtitle"
msgstr ""
# If user gets prompted to choose between several audio/subtitle tracks and
# language is unknown
@ -1419,8 +1668,8 @@ msgstr "Sincronizar"
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgstr "objetos"
msgid "Synching playlists"
msgstr "Sincronizando listas"
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}!
@ -1444,8 +1693,8 @@ msgid ""
"Do you want to replace your custom user ratings with an indicator of how "
"many versions of a media item you posses?"
msgstr ""
"¿Quiere reemplazar su valoración personalizada con cuántas versione posee de"
" un elemento de medios?"
"¿Quiere reemplazar su valoración personalizada por cuántas versiones posee "
"de un elemento de medios?"
# In PKC Settings under Sync
msgctxt "#39719"

View file

@ -1,13 +1,14 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2017
# Croneter None <croneter@gmail.com>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: PlexKodiConnect\n"
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2017\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2020\n"
"Language-Team: Spanish (Mexico) (https://www.transifex.com/croneter/teams/73837/es_MX/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -26,7 +27,7 @@ msgstr "Dirección del servidor (IP)"
msgctxt "#30001"
msgid "Searching for PMS"
msgstr ""
msgstr "Buscando el PMS"
msgctxt "#30002"
msgid "Preferred playback method"
@ -40,6 +41,15 @@ msgid ""
"Warning: Kodi setting \"Play next video automatically\" is enabled. This "
"could break PKC. Deactivate?"
msgstr ""
"Advertencia: El ajuste de Kodi \"Reproducir el siguiente video "
"automáticamente\" está activado. Esto puede dañar PKC. ¿Desactivar?"
msgctxt "#30004"
msgid ""
"The Kodi webserver is needed for artwork caching. PKC already set a strong, "
"random password automatically if you haven't done so already. Please confirm"
" the next dialog that you want to enable the webserver now with Yes."
msgstr ""
msgctxt "#30005"
msgid "Username: "
@ -48,32 +58,33 @@ msgstr "Usuario: "
# Sync notification displayed if there is still artwork to be cached to Kodi
msgctxt "#30006"
msgid "Caching %s Plex images"
msgstr ""
msgstr "Hachiendo caché en %s imágenes de Plex"
# Sync notification displayed if syncing of major artwork is done
msgctxt "#30007"
msgid "Plex image caching done"
msgstr ""
msgstr "El caché de imágenes de Plex fue completado"
# PKC settings artwork: Enable notifications for artwork image sync
msgctxt "#30008"
msgid "Enable notifications for image caching"
msgstr ""
msgstr "Activar notificaciones para caché de imágenes"
# PKC settings artwork: Enable image caching during Kodi playback
msgctxt "#30009"
msgid "Enable image caching during Kodi playback (restart Kodi!)"
msgstr ""
"Activar caché de imágenes durante la reproducción en Kodi (reinicie Kodi)"
# PKC settings - Artwork
msgctxt "#30010"
msgid "Approximate progress"
msgstr ""
msgstr "Progreso aproximado"
# PKC settings - Artwork
msgctxt "#30011"
msgid "Plex artwork (posters and backgrounds) left to cache:"
msgstr ""
msgid "Artwork left to cache:"
msgstr "Arte pendiente de caché:"
# Button text
msgctxt "#30012"
@ -89,10 +100,10 @@ msgctxt "#30014"
msgid "Connection"
msgstr "Conexión"
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgstr ""
msgid "Fanart download already running"
msgstr "La descarga de fondos ya esta en marcha"
msgctxt "#30016"
msgid "Device Name"
@ -106,18 +117,33 @@ msgstr "No autorizado para PMS"
# Sync notification displayed for the number of fanart.tv lookups left
msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr ""
msgstr "Verificando en Fanart.tv %s ítems"
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr ""
msgstr "Búsqueda en fanart.tv completada"
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr "Sincronizar listas de reproducción de Plex (reiniciar Kodi!)"
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr "Sólo sincronizar listas de reproducción específicas de Plex con Kodi"
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr "Avanzado"
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr "Sólo sincronizar listas de reproducción específicas de Kodi con Plex"
msgctxt "#30024"
msgid "Username"
msgstr "Usuario"
@ -126,6 +152,33 @@ msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr "Mostrar mensaje si el PMS está desconectado"
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr ""
"Prefijo en el nombre de la lista de reproducción en Plex para designer la "
"sincronización"
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr ""
"Prefijo en el nombre de la lista de reproducción de Kodi para designer la "
"sincronización"
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "El caché de imágenes solo-PKC fue completado"
# Warning shown when PKC switches to the Kodi default skin Estuary
msgctxt "#30029"
msgid ""
"To ensure a smooth PlexKodiConnect experience, it is HIGHLY recommended to "
"use Kodi's default skin \"Estuary\" for initial set-up and for possible "
"database resets. Continue?"
msgstr ""
msgctxt "#30030"
msgid "Port Number"
msgstr "Número de puerto"
@ -225,6 +278,10 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr "Calidad de vídeo si es necesario Transcodificar"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
msgctxt "#30165"
msgid "Direct Play"
msgstr "Reproducción Directa"
@ -281,6 +338,46 @@ msgctxt "#30198"
msgid "Search"
msgstr "Buscar"
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid ""
"In the following window, enter the server's hostname (or IP) where your Plex"
" media resides. Mind the case!"
msgstr ""
"En la siguiente Ventana, digite el nombre de host del servidor (o dirección "
"IP) donde reside su media de Plex. ¡Tenga cuidado con mayúsculas!"
# For setting up direct paths and adding network credentials - input window
# for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr "Digite el nombre de host del servidor (o dirección IP)"
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid ""
"In the following window, enter the network protocol you would like to use. "
"This is likely 'smb'."
msgstr ""
"En la siguiente Ventana, digite el protocolo de red que desea utilizer. Esto"
" es probablemente 'smb'."
# For setting up direct paths and adding network credentials - input window
# protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr "Digite protocolo de ted"
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr "El nombre de host o dirección IP '{0}' que usted digitó no es válido."
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr "El protocolo '{0}' que usted digitó no está soportado."
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -394,6 +491,14 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr "Configuración para el servidor Plex"
msgctxt "#30417"
msgid ""
"Could not change the Kodi settings file {0}. PKC might not work correctly. "
"Error: {1}"
msgstr ""
"No se ha podido cambiar el archivo de ajustes de Kodi {0}. PKC puede no "
"funcionar correctamente. Error: {1}"
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -404,10 +509,16 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr "Certificado SSL de cliente"
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
msgstr "[COLOR yellow] Restablecer intentos de inicio de sesión[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr "Sincronizar el arte de Plex desde PMS (recomendado)"
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr ""
"Fallo al validar el certificado SSL. Consulte {0} para ver soluciones."
# PKC Settings, category name
msgctxt "#30506"
@ -441,13 +552,20 @@ msgstr "Modo de reproducción"
# PKC Settings - Artwork
msgctxt "#30512"
msgid "Cache all artwork for a smooth Kodi experience"
msgstr ""
msgstr "Hacer caché de todo el arte para una experiencia óptima en Kodi"
# PKC Settings - Artwork
msgctxt "#30513"
msgid "Limit artwork cache threads (recommended for rpi)"
msgstr "Limitar hilos de caché de arte (recomendado para rpi)"
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr ""
"Mostrar todos los extras de Plex en vez de reproducir inmediatamente los "
"trailers"
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -460,8 +578,8 @@ msgstr "Reproducción"
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgstr "[COLOR yellow] Digitar credenciales de red[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr "Establezca credenciales de red para Direct Paths y direct play"
# PKC Settings - Playback
msgctxt "#30518"
@ -476,7 +594,7 @@ msgstr "Preguntar si reproducir tráilers"
# PKC Settings - Plex
msgctxt "#30520"
msgid "Skip PMS delete confirmation (use at your own risk)"
msgstr ""
msgstr "Omitir la confirmación de borrado PMS (utilice a su propio riesgo)"
# PKC Settings - Playback
msgctxt "#30521"
@ -488,6 +606,23 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "Obligar transcodificación de h265/HEVC"
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr ""
"Mostrar tambien el progreso de sincronizacion para el estado de reproduccion"
" y datos del usuario"
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Seleccionar librerias de Plex para sincronizar"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -522,6 +657,7 @@ msgstr "Mensajes del servidor"
msgctxt "#30535"
msgid "Generate a new unique Plex device Id (e.g. to clone Kodi)"
msgstr ""
"Generar un nuevo id de dispositivo único de Plex (ej. para clonar Kodi)"
# PKC Settings - Connection
msgctxt "#30536"
@ -537,6 +673,8 @@ msgstr "REINICIAR KODI SI HACE CAMBIOS"
msgctxt "#30538"
msgid "Manual complete reset of Kodi database necessary, see \"Advanced\""
msgstr ""
"Se require un restablecimiento manual complete de la base de datos de Kodi, "
"vea \"Avanzado\""
# PKC Settings - Artwork
msgctxt "#30539"
@ -550,14 +688,19 @@ msgstr "Descargar arte de sagas de FanArtTV"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "No solicitar elegir un stream o una calidad en particular"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr "Escoger siempre la mejor calidad para tráilers"
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr "Preferir arte de Kodi para colecciones/sagas"
msgctxt "#30544"
msgid "Artwork"
msgstr "Arte"
@ -567,6 +710,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Obligar transcodificar fotografías"
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr ""
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr ""
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr ""
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -587,15 +745,29 @@ msgctxt "#33003"
msgid "Server is online"
msgstr "Servidor está en línea"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr ""
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr ""
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "Nombre de usuario o contraseña incorrecto"
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgstr ""
"No se pudo autenticar demasiadas veces. Restablecer en la configuración."
msgid "User is unauthorized for server {0}"
msgstr "El usuario no esta autorizado para el servidor {0}"
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr "Plex.tv no proveyó una lista válida de usuarios de Plex, disculpas."
# Dialog before playback
msgctxt "#33013"
@ -652,7 +824,7 @@ msgstr "Tamaño de subtítulos incrustados"
# PKC Settings - Sync
msgctxt "#39003"
msgid "Number of simultaneous download threads"
msgstr ""
msgstr "Cantidad de hilos simultáneos de descarga"
# PKC Settings - Plex
msgctxt "#39004"
@ -728,18 +900,20 @@ msgstr ""
msgctxt "#39018"
msgid "Repair the Kodi database (force update all content)"
msgstr ""
"Reparar la base de datos de Kodi (actualización forzosa de todo el "
"contenido)"
# PKC Settings - Advanced
msgctxt "#39019"
msgid "Reset the Kodi database and optionally reset PlexKodiConnect"
msgstr ""
"Restablecer la base de datos de Kodi y oopcionalmente restablecer "
"PlexKodiConnect"
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgstr ""
"[COLOR yellow] Hacer caché ahora a todas las imágenes al caché de texturas "
"de Kodi[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr "Hacer caché ahora de todas las imágenes al Kodi texture cache"
# Appended to a listed PMS if it is in the same LAN network as PKC
msgctxt "#39022"
@ -796,6 +970,8 @@ msgid ""
"Kodi cannot locate the file %s. Please verify your PKC settings. Stop "
"syncing?"
msgstr ""
"Kodi no puede localizer el archivo %s. Por favor verificar sus ajustes de "
"PKC. ¿Detener la sincronización?"
# Pop-up on initial sync
msgctxt "#39033"
@ -820,6 +996,16 @@ msgstr ""
"Reemplazar rutas Plex /volume1/media o \\\\myserver\\media con rutas SMB "
"personalizadas smb://NAS/mystuff"
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr "Escapar caracteres especiales en la ruta (p. ej. espacio a %20)"
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr "Caracteres seguros para urls http(s), dav(s) y (s)ftp"
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -896,8 +1082,8 @@ msgstr "¿Nada funciona? ¡Intente un reset completo!"
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgstr "[COLOR yellow] Elegir servidor Plex de una lista[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr "Elegir el Servidor Plex de una lista"
# PKC Settings - Sync
msgctxt "#39051"
@ -926,19 +1112,15 @@ msgstr "Buscando servidor Plex"
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgstr "Utilizado por la Sincronización al intentar Reproducción Directa"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
# PKC Settings, category name
msgctxt "#39057"
msgid "Customize Paths"
msgstr "Personalizar rutas"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Extender vista de series \"On Deck\" de Plex a todoas las series"
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -975,16 +1157,12 @@ msgstr "Añadidos Recientemente: Además mostrar episodios ya vistos"
# PKC Settings - Appearance Tweaks
msgctxt "#39065"
msgid "Force-refresh Kodi skin on stopping playback"
msgstr ""
msgstr "Refrescar el skin de Kodi al detener la reproducción"
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr ""
"Añadidos Recientemente: También mostrar películas ya vistas (¡Actualize las "
"listas de reproducción/nodos Plex!)"
msgid "Recently Added: Also show already watched movies"
msgstr "Recién Añadido: Mostrar tambien películas ya vistas"
# PKC Settings - Connection
msgctxt "#39067"
@ -993,10 +1171,8 @@ msgstr "El Plex Media Server actual:"
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgstr ""
"[COLOR yellow] Introducir manualmente la dirección del Plex Media "
"Server[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr "Digitar manualmente la dirección del Plex Media Server"
# PKC Settings - Connection
msgctxt "#39069"
@ -1013,6 +1189,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Estado actual de plex.tv:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1023,10 +1204,10 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Series"
# PKC Settings - Playback
# PKC Settings - Sync
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgstr "Siempre use subtítulos de Plex por defecto si es posible"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
@ -1040,9 +1221,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgstr ""
"Número de elementos del PMS a mostrar en widgets (por ejemplo \"On Deck\")"
msgid "Maximum number of videos to show in widgets"
msgstr "Número máximo de videos a mostrar en los widgets"
# PKC Settings - Plex
msgctxt "#39078"
@ -1059,6 +1239,68 @@ msgstr ""
"Plex Companion no pudo abrir el puerto GDM. Por favor cámbielo en los "
"ajustes de PKC."
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
# identical!
msgctxt "#39080"
msgid ""
"Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if "
"you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
"¿Utilizar Add-on Paths (por defecto, fácil) o Direct Paths? Escoja Add-on "
"Paths si no está seguro(a). ¡PKC no funcionará si su configuración de "
"Direct Paths está incorrecta!"
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr "Add-on Paths"
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr "Direct Paths"
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr "Introduzca la URL o IP del PMS"
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr "Introduzca el puerto del PMS"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr "Recargar Kodi para aplicar todos los ajustes."
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr ""
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr "Terminar sesión del usuario de Plex Home "
@ -1067,10 +1309,6 @@ msgctxt "#39201"
msgid "Settings"
msgstr "Opciones"
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr "Actualizar listas de reproducción/nodos de Plex"
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr "Realizar sincronización manual de la bibilioteca"
@ -1100,10 +1338,8 @@ msgstr "No se pudo restablecer PKC. Trate de reiniciar Kodi."
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgstr ""
"[COLOR yellow] Alterne Inicio de sesión de plex.tv (ingresar o "
"salir)[/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr "Alternar login de plex.tv (entrar o salir)"
msgctxt "#39210"
msgid "Not yet connected to Plex Server"
@ -1113,21 +1349,19 @@ msgctxt "#39211"
msgid "Watch later"
msgstr "Ver Luego"
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by
# e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgstr "está desconectado"
msgid "{0} offline"
msgstr "{0} fuera de linea"
msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "Escriba el IP o URL de su Plex Media Server, por ejemplo:"
msgctxt "#39217"
msgid ""
"Does your Plex Media Server support SSL connections? (https instead of "
"http)?"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr ""
"¿Su Plex Media Server admite conexiones SSL? ¿(https en lugar de http)?"
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1147,8 +1381,8 @@ msgid "plex.tv toggle successful"
msgstr "alternado de plex.tv exitoso"
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgstr "[COLOR yellow Buscar ahora fanart faltante en FanartTV[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr "Buscar fanart faltante en fanart.tv ahora"
msgctxt "#39223"
msgid ""
@ -1176,11 +1410,26 @@ msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr "Conectado a plex.tv"
# Message in the PKC settings to display the plex.tv username. Leave the colon
# :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgstr "Usuario de Plex:"
msgid "Plex admin user"
msgstr "Administrador de Plex"
# Error message if user could not log in; the actual user name will be
# appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr "Inicio de session con plex.tv falló para el usuario"
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr "Usuario de Plex home registrado"
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr "Cambiar usuario de Plex"
msgctxt "#39250"
msgid ""
@ -1253,12 +1502,11 @@ msgstr ""
msgctxt "#39403"
msgid ""
"Cancelling the database syncing process. Current Kodi version is "
"unsupported. Please verify your logs for more info."
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
"Cancelando proceso de sincronización de la base de datos. La versión de Kodi"
" actual no es compatible. Por favor, compruebe la bitácora para obtener más "
"información."
"La versión actual de Kodi no está soportada por PKC. Por favor consultar el"
" fórum de Plex."
msgctxt "#39405"
msgid "Plex playlists/nodes refreshed"
@ -1268,10 +1516,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr "Error de actualización de listas de reproducción/nodos de Plex"
msgctxt "#39407"
msgid "Full library sync finished"
msgstr "Sincronización completa de la biblioteca terminada"
msgctxt "#39408"
msgid ""
"Sync had to skip some items because they could not be processed. Kodi may be"
@ -1303,6 +1547,10 @@ msgctxt "#39501"
msgid "Collections"
msgstr "Sagas"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr "On Deck de PKC (más rápido)"
msgctxt "#39600"
msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1361,11 +1609,10 @@ msgctxt "#39705"
msgid "Use at your own risk"
msgstr "Usar a su propio riesgo"
# If user gets prompted to choose between several subtitles. Leave the number
# one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgstr "1 Sin subtitulos"
msgid "Don't burn-in any subtitle"
msgstr ""
# If user gets prompted to choose between several audio/subtitle tracks and
# language is unknown
@ -1419,8 +1666,8 @@ msgstr "Sincronizar"
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgstr "objetos"
msgid "Synching playlists"
msgstr "Sincronizando listas"
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}!
@ -1444,8 +1691,8 @@ msgid ""
"Do you want to replace your custom user ratings with an indicator of how "
"many versions of a media item you posses?"
msgstr ""
"¿Quiere reemplazar su valoración personalizada con cuántas versione posee de"
" un elemento de medios?"
"¿Quiere reemplazar su valoración personalizada por cuántas versiones posee "
"de un elemento de medios?"
# In PKC Settings under Sync
msgctxt "#39719"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,15 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2017
# Croneter None <croneter@gmail.com>, 2019
# Savage93 <savageistheking@gmail.com>, 2021
#
msgid ""
msgstr ""
"Project-Id-Version: PlexKodiConnect\n"
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2017\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Savage93 <savageistheking@gmail.com>, 2021\n"
"Language-Team: Hungarian (Hungary) (https://www.transifex.com/croneter/teams/73837/hu_HU/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -43,6 +45,17 @@ msgstr ""
"Figyelem: \"A következő videó automatikus lejátszása\" be van kapcsolva. Ez "
"megakadályozhatja a PKC megfelelő működését. Kikapcsolja?"
msgctxt "#30004"
msgid ""
"The Kodi webserver is needed for artwork caching. PKC already set a strong, "
"random password automatically if you haven't done so already. Please confirm"
" the next dialog that you want to enable the webserver now with Yes."
msgstr ""
"A művészképek gyorsítótárazásához szükség van a Kodi webszerverének "
"bekapcsolására. A PKC beállított egy erős, véletlenszerű jelszót ehhez, "
"amennyiben ezt korábban nem tette meg. Kérem erősítse meg a következő "
"dialógusablakban, hogy be kívánja kapcsolni a webszervert."
msgctxt "#30005"
msgid "Username: "
msgstr "Felhasználónév: "
@ -50,32 +63,34 @@ msgstr "Felhasználónév: "
# Sync notification displayed if there is still artwork to be cached to Kodi
msgctxt "#30006"
msgid "Caching %s Plex images"
msgstr ""
msgstr "%s Plex kép gyorsítótárazása"
# Sync notification displayed if syncing of major artwork is done
msgctxt "#30007"
msgid "Plex image caching done"
msgstr ""
msgstr "a Plex képek gyorsítótárazása befejeződött"
# PKC settings artwork: Enable notifications for artwork image sync
msgctxt "#30008"
msgid "Enable notifications for image caching"
msgstr ""
msgstr "A képek gyorsítótárazásával kapcsolatos értesítés engedélyezése"
# PKC settings artwork: Enable image caching during Kodi playback
msgctxt "#30009"
msgid "Enable image caching during Kodi playback (restart Kodi!)"
msgstr ""
"A képek gyorsítótárazásának engedélyezése lejátszás alatt (indítsa újra a "
"Kodi-t!)"
# PKC settings - Artwork
msgctxt "#30010"
msgid "Approximate progress"
msgstr ""
msgstr "Hozzávetőleges állapot"
# PKC settings - Artwork
msgctxt "#30011"
msgid "Plex artwork (posters and backgrounds) left to cache:"
msgstr ""
msgid "Artwork left to cache:"
msgstr "Hátralévő gyorsítótárazásra váró elemek:"
# Button text
msgctxt "#30012"
@ -91,10 +106,10 @@ msgctxt "#30014"
msgid "Connection"
msgstr "Kapcsolat"
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgstr ""
msgid "Fanart download already running"
msgstr "Már fut a művészképek letöltése"
msgctxt "#30016"
msgid "Device Name"
@ -108,18 +123,33 @@ msgstr "PMS hitelesítés sikertelen"
# Sync notification displayed for the number of fanart.tv lookups left
msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr ""
msgstr "%s elem keresése a FanartTV-n"
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr ""
msgstr "A FanartTV lekérdezés befejeződött"
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr "Plex lejátszási listák szinkronizálása (indítsa újra a Kodi-t!)"
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr "Csak a meghatározott lejátszási listák szinkronizálása a Kodi-val"
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr "Haladó"
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr "Csak a meghatározott lejátszási listák szinkronizálása a Plex-szel"
msgctxt "#30024"
msgid "Username"
msgstr "Felhasználónév"
@ -128,6 +158,29 @@ msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr "Üzenet megjelenítése ha a PMS elérhetetlenné válik"
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr "Plex lejátszási lista előtag szinkronizálásra kijelöléshez"
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr "Kodi lejátszási lista előtag szinkronizálásra kijelöléshez"
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "PKC képek gyorsítótárazása befejeződött"
# Warning shown when PKC switches to the Kodi default skin Estuary
msgctxt "#30029"
msgid ""
"To ensure a smooth PlexKodiConnect experience, it is HIGHLY recommended to "
"use Kodi's default skin \"Estuary\" for initial set-up and for possible "
"database resets. Continue?"
msgstr ""
msgctxt "#30030"
msgid "Port Number"
msgstr "Portszám"
@ -227,6 +280,12 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr "Videóminőség, ha transzkódolás szükséges"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
"Transzkódolás minőségének automatikus beállítása (kapcsolja ki Chromecasttal"
" való használatkor)"
msgctxt "#30165"
msgid "Direct Play"
msgstr "Közvetlen lejátszás"
@ -283,6 +342,47 @@ msgctxt "#30198"
msgid "Search"
msgstr "Keresés"
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid ""
"In the following window, enter the server's hostname (or IP) where your Plex"
" media resides. Mind the case!"
msgstr ""
"A következő ablakban adja meg a szerver kiszolgálónevét (vagy IP címét) "
"amelyen a Plex médiatartalmak megtalálhatók. Figyeljen a kis- és "
"nagybetűkre!"
# For setting up direct paths and adding network credentials - input window
# for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr "Adja meg a szerver kiszolgálónevét (vagy IP címét)"
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid ""
"In the following window, enter the network protocol you would like to use. "
"This is likely 'smb'."
msgstr ""
"A következő ablakban adja meg a használni kívánt hálózati protokollt. Ez "
"valószínűleg az 'smb'."
# For setting up direct paths and adding network credentials - input window
# protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr "Adja meg a hálózati protokollt"
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr "A megadott kiszolgálónév vagy IP cím '{0}' helytelen."
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr "A megadott hálózati protokoll ('{0}') nem támogatott."
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -395,6 +495,14 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr "Plex szerver beállításai"
msgctxt "#30417"
msgid ""
"Could not change the Kodi settings file {0}. PKC might not work correctly. "
"Error: {1}"
msgstr ""
"A {0} Kodi beállításfájl megváltoztatása sikertelen. Lehet, hogy a PKC nem "
"fog megfelelően működni.Hiba: {1}"
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -405,11 +513,17 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr "Kliens SSL tanúsítvány"
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr "Plex művészképek szinkronizálása a szerverről (ajánlott)"
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr ""
"[COLOR yellow]Bejelentkezési kísérletek számának visszaállítása[/COLOR]"
"Az SSL tanúsítvány hitelesítése sikertelen. Kérem ellenőrizze a(z) {0}-t a "
"megoldásokért."
# PKC Settings, category name
msgctxt "#30506"
@ -443,7 +557,7 @@ msgstr "Lejátszási mód"
# PKC Settings - Artwork
msgctxt "#30512"
msgid "Cache all artwork for a smooth Kodi experience"
msgstr ""
msgstr "Az összes művészkép gyorsítótárazása a gördülékeny Kodi élményért"
# PKC Settings - Artwork
msgctxt "#30513"
@ -452,6 +566,11 @@ msgstr ""
"Képek gyorsítótárazására használt processzorszálak korlátozása (Raspberry Pi"
" esetén javasolt)"
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr "Az összes Plex extra mutatása az azonnali előzetes-lejátszás helyett"
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -464,8 +583,10 @@ msgstr "Lejátszás"
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgstr "[COLOR yellow]Hálózati hitelesítő adatok megadása[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr ""
"Hálózati hozzáférési adatok megadása a közvetlen útvonalakhoz és közvetlen "
"lejátszáshoz"
# PKC Settings - Playback
msgctxt "#30518"
@ -492,6 +613,23 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "h265/HEVC transzkódolás kényszerítése"
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr ""
"Szinkronizálási folyamat állapotának mutatása a lejátszási- és felhasználói "
"adatok esetén is"
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Szinkronizálni kívánt Plex könyvtárak kiválasztása"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr "Bevezető kihagyása"
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -525,7 +663,7 @@ msgstr "Szerverüzenetek"
# PKC Settings - Advanced
msgctxt "#30535"
msgid "Generate a new unique Plex device Id (e.g. to clone Kodi)"
msgstr ""
msgstr "Új egyéni Plex eszközazonosító generálása (pl. Kodi klónozásához)"
# PKC Settings - Connection
msgctxt "#30536"
@ -542,7 +680,7 @@ msgstr "INDÍTSA ÚJRA A KODI-T HA BÁRMIT ÁTÁLLÍT"
# PKC Settings warning
msgctxt "#30538"
msgid "Manual complete reset of Kodi database necessary, see \"Advanced\""
msgstr ""
msgstr "A Kodi adatbázis kézi visszaállítása szükséges, lásd \"Haladó\""
# PKC Settings - Artwork
msgctxt "#30539"
@ -556,14 +694,21 @@ msgstr "Film-szett/kollekció képek letöltése a FanArtTV-ről"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Ne kérdezze meg melyik stream/minőség kerüljön lejátszásra"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
"Transzkódolás: hang- és feliratsávok automatikus kiválasztása a Plex "
"alapértelmezések alapján"
# PKC Settings - Playback
msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr "Mindig válassza a legjobb minőséget filmelőzetesek lejátszásakor"
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr "Kodi művészképek előnyben részesítése kollekcióknál/gyűjteményeknél"
msgctxt "#30544"
msgid "Artwork"
msgstr "Művészképek"
@ -573,6 +718,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Képek transzkódolásának kényszerítése"
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr ""
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr ""
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr ""
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -593,15 +753,29 @@ msgctxt "#33003"
msgid "Server is online"
msgstr "A szerver elérhető"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr "PMS kényszerített transzkódolás"
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr "PMS kényszerített közvetlen streamelés"
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "Hibás felhasználónév vagy jelszó"
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgstr ""
"Túl sok sikertelen bejelentkezési kísérlet. Állítsa vissza a beállításoknál."
msgid "User is unauthorized for server {0}"
msgstr "A felhasználó nem jogosult a {0} szerverhez való hozzáférésre"
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr "A Plex.tv sajnos nem adta meg a jogosult Plex felhasználók listáját"
# Dialog before playback
msgctxt "#33013"
@ -658,7 +832,7 @@ msgstr "Égetett felirat mérete"
# PKC Settings - Sync
msgctxt "#39003"
msgid "Number of simultaneous download threads"
msgstr ""
msgstr "Egyidejűleg futó letöltési szálak száma"
# PKC Settings - Plex
msgctxt "#39004"
@ -734,18 +908,19 @@ msgstr ""
# PKC Settings - Advanced
msgctxt "#39018"
msgid "Repair the Kodi database (force update all content)"
msgstr ""
msgstr "Kodi adatbázis javítása (minden tartalom kényszerített frissítése)"
# PKC Settings - Advanced
msgctxt "#39019"
msgid "Reset the Kodi database and optionally reset PlexKodiConnect"
msgstr ""
"Kodi adatbázis visszaállítása és a PlexKodiConnect választható "
"visszaállítása"
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgstr ""
"[COLOR yellow]Minden kép gyorsítótárazása a Kodi gyorsítótárába most[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr "Minden kép gyorsítótárazása a Kodi gyorsítótárába most"
# Appended to a listed PMS if it is in the same LAN network as PKC
msgctxt "#39022"
@ -828,6 +1003,17 @@ msgstr ""
"Lecseréli a Plex útvonalakat (/volume1/media vagy \\\\myserver\\media) "
"egyéni SMB útvonalakra (smb://NAS/mystuff)"
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr ""
"A speciális karakterek feloldása az elérési útban (pl. szóköz helyett %20)"
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr "Biztonságos karakterek http(s), dav(s) és (s)ftp elérési utakhoz"
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -903,8 +1089,8 @@ msgstr "Semmi sem működik? Próbálja meg a teljes visszaállítást!"
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgstr "[COLOR yellow]Válasszon Plex szervert a listából[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr "Válasszon Plex szervert a listából"
# PKC Settings - Sync
msgctxt "#39051"
@ -933,21 +1119,17 @@ msgstr "Plex szerver keresése"
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgstr "Szinkronizáció és közvetlen lejátszás esetén használt"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
"Szinkronizáció és közvetlen elérési utak használata esetén szükséges. "
"Módosítás esetén indítsa újra a Kodi-t!"
# PKC Settings, category name
msgctxt "#39057"
msgid "Customize Paths"
msgstr "Útvonalak testreszabása"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr ""
"A Plex TV sorozatok \"A fedélzeten\" nézetének kiterjesztése az összes "
"sorozatra"
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -987,12 +1169,8 @@ msgstr "Kodi felület kényszerített frissítése a lejátszás megállításak
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr ""
"Mostanában hozzáadott: már látott filmek mutatása (frissítse a Plex "
"lejátszási listát/csomópontokat!)"
msgid "Recently Added: Also show already watched movies"
msgstr "Mostanában hozzáadott: már látott filmek mutatása"
# PKC Settings - Connection
msgctxt "#39067"
@ -1001,8 +1179,8 @@ msgstr "Az Ön jelenlegi Plex médiaszervere:"
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgstr "[COLOR yellow]Plex médiaszerver címének kézi megadása[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr "Plex médiaszerver címének kézi megadása"
# PKC Settings - Connection
msgctxt "#39069"
@ -1019,6 +1197,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Jelenlegi plex.tv állapot:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1029,11 +1212,10 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "TV sorozatok"
# PKC Settings - Playback
# PKC Settings - Sync
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgid "Verify access to media files while synching"
msgstr ""
"Mindig használja az alapértelmezett Plex feliratot amennyiben lehetséges"
# Pop-up during initial sync
msgctxt "#39076"
@ -1047,8 +1229,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgstr "Megjelenítendő elemek száma a widgetekben (pl. \"A fedélzeten\")"
msgid "Maximum number of videos to show in widgets"
msgstr "A widgetekben megjelenített videók maximális száma"
# PKC Settings - Plex
msgctxt "#39078"
@ -1064,18 +1246,78 @@ msgstr ""
"A Plex Companion nem tudta megnyitni a GDM portot. Kérem változtassa meg a "
"PKC beállításokban."
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
# identical!
msgctxt "#39080"
msgid ""
"Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if "
"you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
"Bővítmény elérési utakat (alapértelmezett, egyszerű) vagy közvetlen elérési "
"utakat kíván használni? Ha nem biztos benne, válassza a bővítmény elérési "
"utakat. A PKC nem fog működni, ha a közvetlen elérési utak rosszul vannak "
"beállítva!"
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr "Bővítmény elérési utak"
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr "Közvetlen elérési utak"
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr "Adja meg a PMS IP-címét vagy URL-jét"
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr "Adja meg a PMS portját"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
"Kodi csomópont fájlok újratöltése az alábbi beállítások alkalmazásához"
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr ""
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr "Kijelentkezés az otthoni Plex felhasználó fiókból"
msgstr "Kijelentkezés az otthoni Plex felhasználó fiókból: "
msgctxt "#39201"
msgid "Settings"
msgstr "Beállítások"
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr "Plex lejátszási listák/csomópontok frissítése"
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr "Kézi könyvtárszinkronizáció végrehajtása"
@ -1105,10 +1347,8 @@ msgstr "A PKC visszaállítása sikertelen. Kérem indítsa újra a Kodi-t."
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgstr ""
"[COLOR yellow]Plex.tv bejelentkezés átkapcsolása (bejelentkezés vagy "
"kijelentkezés)[/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr "Plex.tv bejelentkezés átkapcsolása (be- vagy kijelentkezés)"
msgctxt "#39210"
msgid "Not yet connected to Plex Server"
@ -1118,22 +1358,21 @@ msgctxt "#39211"
msgid "Watch later"
msgstr "Később nézendő"
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by
# e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgstr "nem elérhető"
msgid "{0} offline"
msgstr "a(z) {0} nem elérhető"
msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "Adja meg a Plex médiaszerver IP-címét vagy URL-jét, például:"
msgctxt "#39217"
msgid ""
"Does your Plex Media Server support SSL connections? (https instead of "
"http)?"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr ""
"Támogatja a Plex médiaszervere az SSL csatlakozásokat (https a http "
"helyett)?"
"Kíván HTTPS (SSL) kapcsolatokat használni? Ha nem biztos benne válassza az "
"igent."
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1153,8 +1392,8 @@ msgid "plex.tv toggle successful"
msgstr "plex.tv átkapcsolás sikeres"
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgstr "[COLOR yellow]Hiányzó művészképek keresése a FanArtTV-n most[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr "Hiányzó művészképek keresése a FanArtTV-n most"
msgctxt "#39223"
msgid ""
@ -1182,11 +1421,26 @@ msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr "Be van jelentkezve a plex.tv-be"
# Message in the PKC settings to display the plex.tv username. Leave the colon
# :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgstr "Plex felhasználó:"
msgid "Plex admin user"
msgstr "Plex adminisztrátor felhasználó"
# Error message if user could not log in; the actual user name will be
# appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr "Sikertelen bejelentkezés a plex.tv-re a következő felhasználóval"
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr "Bejelentkezett otthoni Plex felhasználó"
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr "Bejelentkezett otthoni Plex felhasználó megváltoztatása"
msgctxt "#39250"
msgid ""
@ -1259,11 +1513,11 @@ msgstr " lehet nem fog megfelelően működni az adatbázis visszaállításáig
msgctxt "#39403"
msgid ""
"Cancelling the database syncing process. Current Kodi version is "
"unsupported. Please verify your logs for more info."
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
"Az adatbázis szinkronizációs folyamat megszakítása. A jelenlegi Kodi verzió "
"nem támogatott. Kérem tekintse meg a hibanaplót további információkért."
"A PKC nem támogatja a jelenlegi Kodi verziót. Kérjen tanácsot a Plex "
"fórumon."
msgctxt "#39405"
msgid "Plex playlists/nodes refreshed"
@ -1273,10 +1527,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr "a Plex lejátszási listák/csomópontok frissítése sikertelen"
msgctxt "#39407"
msgid "Full library sync finished"
msgstr "Teljes könyvtárszinkronizáció befejezve"
msgctxt "#39408"
msgid ""
"Sync had to skip some items because they could not be processed. Kodi may be"
@ -1308,6 +1558,10 @@ msgctxt "#39501"
msgid "Collections"
msgstr "Gyűjtemények"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr "PKC \"a fedélzeten\" (gyorsabb)"
msgctxt "#39600"
msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1367,11 +1621,10 @@ msgctxt "#39705"
msgid "Use at your own risk"
msgstr "Csak saját felelősségre használja"
# If user gets prompted to choose between several subtitles. Leave the number
# one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgstr "1 Feliratok kikapcsolása"
msgid "Don't burn-in any subtitle"
msgstr "Ne égessen be feliratot"
# If user gets prompted to choose between several audio/subtitle tracks and
# language is unknown
@ -1425,8 +1678,8 @@ msgstr "Szinkronizáció"
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgstr "elemek"
msgid "Synching playlists"
msgstr "Lejátszási listák szinkronizálása"
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1,13 +1,17 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2017
# Angela Calò <angycalo@libero.it>, 2018
# Cristiano Bozzi <c.bozzi@nextworks.it>, 2018
# Luigi Mantellini <luigi.mantellini@gmail.com>, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: PlexKodiConnect\n"
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2017\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Luigi Mantellini <luigi.mantellini@gmail.com>, 2019\n"
"Language-Team: Italian (Italy) (https://www.transifex.com/croneter/teams/73837/it_IT/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -18,7 +22,7 @@ msgstr ""
# Add-on settings
msgctxt "#29999"
msgid "PlexKodiConnect"
msgstr ""
msgstr "PlexKodiConnect"
msgctxt "#30000"
msgid "Server Address (IP)"
@ -26,7 +30,7 @@ msgstr "Indirizzo Server (IP)"
msgctxt "#30001"
msgid "Searching for PMS"
msgstr ""
msgstr "Ricerca PMS"
msgctxt "#30002"
msgid "Preferred playback method"
@ -40,45 +44,56 @@ msgid ""
"Warning: Kodi setting \"Play next video automatically\" is enabled. This "
"could break PKC. Deactivate?"
msgstr ""
"Attenzione: l'impostazione Kodi \"Avvia il video successivo "
"automaticamente\" è attivata. Questo può interrompere PKC. Disattivare?"
msgctxt "#30004"
msgid ""
"The Kodi webserver is needed for artwork caching. PKC already set a strong, "
"random password automatically if you haven't done so already. Please confirm"
" the next dialog that you want to enable the webserver now with Yes."
msgstr ""
msgctxt "#30005"
msgid "Username: "
msgstr ""
msgstr "Nome utente:"
# Sync notification displayed if there is still artwork to be cached to Kodi
msgctxt "#30006"
msgid "Caching %s Plex images"
msgstr ""
msgstr "Sto salvando %s delle immagini Plex "
# Sync notification displayed if syncing of major artwork is done
msgctxt "#30007"
msgid "Plex image caching done"
msgstr ""
msgstr "Salvataggio dell'immagine Plex completato"
# PKC settings artwork: Enable notifications for artwork image sync
msgctxt "#30008"
msgid "Enable notifications for image caching"
msgstr ""
msgstr "Abilita le notifiche per il salvataggio dell'immagine"
# PKC settings artwork: Enable image caching during Kodi playback
msgctxt "#30009"
msgid "Enable image caching during Kodi playback (restart Kodi!)"
msgstr ""
"Abilita il salvataggio dell'immagine durante il playback Kodi (riavvia "
"Kodi!)"
# PKC settings - Artwork
msgctxt "#30010"
msgid "Approximate progress"
msgstr ""
msgstr "Avanzamento appossimativo"
# PKC settings - Artwork
msgctxt "#30011"
msgid "Plex artwork (posters and backgrounds) left to cache:"
msgstr ""
msgid "Artwork left to cache:"
msgstr "Copertine cachate"
# Button text
msgctxt "#30012"
msgid "OK"
msgstr ""
msgstr "OK"
msgctxt "#30013"
msgid "Never show"
@ -89,10 +104,10 @@ msgctxt "#30014"
msgid "Connection"
msgstr "Connessione"
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgstr ""
msgid "Fanart download already running"
msgstr "Scaricamento Fanart già in esecuzione"
msgctxt "#30016"
msgid "Device Name"
@ -106,26 +121,64 @@ msgstr "Non autorizzato per PMS"
# Sync notification displayed for the number of fanart.tv lookups left
msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr ""
msgstr "Checking %s FanartTV"
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr ""
msgstr "Ricerca FanartTV completata"
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr "Sync Plex playlists (riavvio Kodi!)"
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr "Sincronizza solo specifiche playlist Kodi"
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr "Avanzate"
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr "Sincronizza solo specifiche playlist da Plex"
msgctxt "#30024"
msgid "Username"
msgstr ""
msgstr "Nome Utente"
msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr "Mostra un messaggio se PMS va offline"
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr "Prefisso per sincronizzazione playlist da Plex "
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr "Prefisso per sincronizzazione playlist da Kodi"
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "Cache delle immagini di PKC completato"
# Warning shown when PKC switches to the Kodi default skin Estuary
msgctxt "#30029"
msgid ""
"To ensure a smooth PlexKodiConnect experience, it is HIGHLY recommended to "
"use Kodi's default skin \"Estuary\" for initial set-up and for possible "
"database resets. Continue?"
msgstr ""
msgctxt "#30030"
msgid "Port Number"
msgstr "Porta"
@ -137,7 +190,7 @@ msgstr "Possiedo questo Plex Media Server"
# Kodi context menu entry for movie and episode information screen
msgctxt "#30032"
msgid "Information"
msgstr ""
msgstr "Informazione"
msgctxt "#30042"
msgid "Refresh"
@ -225,6 +278,10 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr "Qualità Video se la Transcodifica è necessaria"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
msgctxt "#30165"
msgid "Direct Play"
msgstr "Riproduzione diretta"
@ -281,6 +338,46 @@ msgctxt "#30198"
msgid "Search"
msgstr "Cerca"
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid ""
"In the following window, enter the server's hostname (or IP) where your Plex"
" media resides. Mind the case!"
msgstr ""
"Nella finestra seguente, inserisci l'hostname del server (oppure l'IP) dove "
"Plex Media Server risiede. Attenzione alle maiuscole!"
# For setting up direct paths and adding network credentials - input window
# for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr "Inserisci l'hostname del server (oppure l'IP)"
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid ""
"In the following window, enter the network protocol you would like to use. "
"This is likely 'smb'."
msgstr ""
"Nella finestra seguente, inserisci il protocollo di rete che vorresti usare."
" Questo è tipicamente \"smb\"."
# For setting up direct paths and adding network credentials - input window
# protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr "Inserisci il protocollo di rete"
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr "L'hostname o IP '{0}' inserito non è valido."
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr "Il protocollo '{0}\" inserito non è supportato."
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -394,6 +491,14 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr "Impostazioni del server Plex"
msgctxt "#30417"
msgid ""
"Could not change the Kodi settings file {0}. PKC might not work correctly. "
"Error: {1}"
msgstr ""
"Impossibile cambiare il file di configurazione di Kodi {0}. PKC potrebbe non"
" funzionare correttamente. Errore: {1}"
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -404,10 +509,16 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr "Certificato SSL del client"
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
msgstr "[COLOR yellow]Resetta i tentativi di login[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr "Sincronizza le locandine Plex da PMS (raccomandato)"
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr ""
"Certificato SSL non valido. Per favore controlla su {0} per risolvere."
# PKC Settings, category name
msgctxt "#30506"
@ -441,13 +552,19 @@ msgstr "Modalità di riproduzione"
# PKC Settings - Artwork
msgctxt "#30512"
msgid "Cache all artwork for a smooth Kodi experience"
msgstr ""
msgstr "cache di tutte le copertine per una visione più fluida da Kodi"
# PKC Settings - Artwork
msgctxt "#30513"
msgid "Limit artwork cache threads (recommended for rpi)"
msgstr "Limita numero thread di caching delle artwork (raccomandato per rpi)"
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr ""
"Mostra tutti i contenuti extra di Plex invece di avviare subito i trailers"
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -460,8 +577,10 @@ msgstr "Riproduzione"
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgstr "[COLOR yellow]Inserisci credenziali di rete[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr ""
"Inserisci le credenziali per accesso diretto ai percorsi e per la lettura "
"diretta"
# PKC Settings - Playback
msgctxt "#30518"
@ -477,6 +596,7 @@ msgstr "Richiedi di riprodurre trailer"
msgctxt "#30520"
msgid "Skip PMS delete confirmation (use at your own risk)"
msgstr ""
"Salta conferma per la cancellazione (usare a proprio rischio e pericolo)"
# PKC Settings - Playback
msgctxt "#30521"
@ -488,6 +608,21 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "Forza transcodifica h265/HEVC"
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr "Mostra l'avanzamento dello stato di lettura e dei dati utente"
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Seleziona le librerie Plex da sincronizzazare"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -522,6 +657,7 @@ msgstr "Messaggi del server"
msgctxt "#30535"
msgid "Generate a new unique Plex device Id (e.g. to clone Kodi)"
msgstr ""
"Genera un nuovo e unico Plex Id per il dispositivo (es. clonazione di Kodi)"
# PKC Settings - Connection
msgctxt "#30536"
@ -537,6 +673,8 @@ msgstr "RIAVVIA KODI SE FAI DELLE MODIFICHE"
msgctxt "#30538"
msgid "Manual complete reset of Kodi database necessary, see \"Advanced\""
msgstr ""
"E' neccessario fare un reset manuale del database di Kodi, cerca in "
"\"Avanzate\""
# PKC Settings - Artwork
msgctxt "#30539"
@ -550,23 +688,43 @@ msgstr "Scarica collezioni/cofanetti film da FanartTV"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Non chiedere di scegliere la qualità dello stream"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr "Scegli sempre qualità migliore per i trailer"
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr "Preferisci immagini di Kodi per collezioni/serie"
msgctxt "#30544"
msgid "Artwork"
msgstr ""
msgstr "Immagine"
# PKC Settings - Playback
msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Forza transcodifica immagini"
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr ""
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr ""
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr ""
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -587,16 +745,29 @@ msgctxt "#33003"
msgid "Server is online"
msgstr "Il server è online"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr ""
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr ""
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "Username o password non validi"
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgstr ""
"Autenticazione fallita per troppe volte. Effettua un reset nelle "
"impostazioni."
msgid "User is unauthorized for server {0}"
msgstr "L'utente non è autorizzato per il server {0}"
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr "Spiacente ma Plex.tv non fornisce alcuna lista di utenti Plex validi."
# Dialog before playback
msgctxt "#33013"
@ -652,7 +823,7 @@ msgstr "Dimensione sottotitoli in sovraimpressione"
# PKC Settings - Sync
msgctxt "#39003"
msgid "Number of simultaneous download threads"
msgstr ""
msgstr "Numero di thread di scaricamento simultanei"
# PKC Settings - Plex
msgctxt "#39004"
@ -729,19 +900,17 @@ msgstr ""
# PKC Settings - Advanced
msgctxt "#39018"
msgid "Repair the Kodi database (force update all content)"
msgstr ""
msgstr "Ripara database di Kodi (forza aggiornamento di tutti i contenuti)"
# PKC Settings - Advanced
msgctxt "#39019"
msgid "Reset the Kodi database and optionally reset PlexKodiConnect"
msgstr ""
msgstr "Reset database di Kodi e opzionalmente reset PlexKodiConnect"
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgstr ""
"[COLOR yelow]Sposta tutte le immagini nella cache delle texture di "
"Kodi[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr "Cache tutte le immagini nella cache delle texture di Kodi adesso"
# Appended to a listed PMS if it is in the same LAN network as PKC
msgctxt "#39022"
@ -771,6 +940,11 @@ msgid ""
"shares need to use direct paths (e.g. smb://myNAS/mymovie.mkv or "
"\\\\myNAS/mymovie.mkv)!"
msgstr ""
"ATTENZIONE! In caso di selezione della modaliatà \"Nativa\", potresti "
"perdere l'acccesso ad alcune funzionalità di Plex come ad esempio: trailer "
"Plex ed opzioni di transcodifica. TUTTE le condivisioni Plex devono usare "
"l'opzione path diretto (es. smb://myNAS/mymovie.mkv or "
"\\\\myNAS/mymovie.mkv)!"
# Pop-up on initial sync
msgctxt "#39029"
@ -795,6 +969,8 @@ msgid ""
"Kodi cannot locate the file %s. Please verify your PKC settings. Stop "
"syncing?"
msgstr ""
"Impossibile trovare il file %s di Kodi. Verificare la configurazione del "
"plugin PKC. Arrestare la sincronizzazione?"
# Pop-up on initial sync
msgctxt "#39033"
@ -802,11 +978,13 @@ msgid ""
"Transform Plex UNC library paths \\\\myNas\\mymovie.mkv automatically to smb"
" paths, smb://myNas/mymovie.mkv? (recommended)"
msgstr ""
"Trasforma i Plex path UNC della libreria \\\\myNas\\mymovie.mkv "
"automatically in path smb, smb://myNas/mymovie.mkv? (raccomandato)"
# PKC Settings - Customize Paths
msgctxt "#39034"
msgid "Replace Plex UNC paths \\\\myNas with smb://myNas"
msgstr ""
msgstr "Sostituisci i path Plex UNC \\\\myNas con smb://myNas"
# PKC Settings - Customize Paths
msgctxt "#39035"
@ -814,6 +992,20 @@ msgid ""
"Replace Plex paths /volume1/media or \\\\myserver\\media with custom SMB "
"paths smb://NAS/mystuff"
msgstr ""
"Sostituisci i Plex path /volume1/media o \\\\myserver\\media con path SMB "
"custom smb://NAS/mystuff"
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr ""
"Esegui l'escape dei caratteri speciali nel percorso (es. \"spazio\" "
"trasformato in \"%20\")"
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39037"
@ -891,8 +1083,8 @@ msgstr "Non funziona nulla? Prova un reset completo!"
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgstr "[COLOR yellow]Scegli un server Plex[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr "Scegli il Server Plex da una lista"
# PKC Settings - Sync
msgctxt "#39051"
@ -922,21 +1114,15 @@ msgstr "Ricerca del server Plex"
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
"Utilizzato dalla sincronizzazione e quando si utilizza la riproduzione "
"diretta"
# PKC Settings, category name
msgctxt "#39057"
msgid "Customize Paths"
msgstr "Personalizza i percorsi"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Estendi la vista \"On Deck\" delle Serie TV a tutte le serie"
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -974,16 +1160,12 @@ msgstr "Aggiunti di recente: mostra anche gli episodi già visti"
# PKC Settings - Appearance Tweaks
msgctxt "#39065"
msgid "Force-refresh Kodi skin on stopping playback"
msgstr ""
msgstr "Forza aggiornamento della skin Kodi dopo lo stop di un contenuto"
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr ""
"Aggiunti di recente: mostra anche i film già visti (Aggiornare playlist/nodi"
" Plex!)"
msgid "Recently Added: Also show already watched movies"
msgstr "Aggiunti di recente: Mostra anche film già visti"
# PKC Settings - Connection
msgctxt "#39067"
@ -992,10 +1174,8 @@ msgstr "Il tuo Plex Media Server corrente:"
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgstr ""
"[COLOR yellow]Inserisci manualmente l'indirizzo del Plex Media "
"Server[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr "Inserisci manualmente l'indirizzo del Plex Media Server"
# PKC Settings - Connection
msgctxt "#39069"
@ -1012,6 +1192,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Stato attuale di plex.tv:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1022,10 +1207,10 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Serie TV"
# PKC Settings - Playback
# PKC Settings - Sync
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgstr "Usa sempre i sottotitoli predefiniti di Plex se possibile"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
@ -1038,8 +1223,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgstr "Numero di elementi PMS da mostrare nei widget (es. \"On Deck\")"
msgid "Maximum number of videos to show in widgets"
msgstr "Numero massimo di video da mostrare nei widget"
# PKC Settings - Plex
msgctxt "#39078"
@ -1055,6 +1240,70 @@ msgstr ""
"Plex Companion non può aprire la porta GDM. Si prega di modificarla nelle "
"impostazioni di PKC."
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
# identical!
msgctxt "#39080"
msgid ""
"Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if "
"you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
"Usa i Percorsi Add-on (default) oppure i Percorsi Diretti? Scegli i Percorsi"
" Add-on se sei non sicuro. PKC non funzionerà se la configurazione dei "
"Percorsi Diretti è errata."
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr "Percorsi Add-on"
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr "Percorsi Diretti"
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr "Inserisci l'URL o l'IP del PMS"
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr "Inserisci la porta del PMS"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
"Ricarica i nodi di file di Kodi per applicare tutte le impostazioni di "
"sotto"
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr ""
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr "Logout utente Plex "
@ -1063,10 +1312,6 @@ msgctxt "#39201"
msgid "Settings"
msgstr "Impostazioni"
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr "Aggiorna playlist/nodi Plex"
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr "Eseguire sincronizzazione manuale della libreria"
@ -1096,8 +1341,10 @@ msgstr "Impossibile resettare PKC. Prova a riavviare Kodi."
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgstr "[COLOR yellow]Login/Logout plex.tv[/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr ""
"Inverti lo stato di autenticazione si plex.tv (autenticato oppure non "
"autenticato)"
msgctxt "#39210"
msgid "Not yet connected to Plex Server"
@ -1107,21 +1354,19 @@ msgctxt "#39211"
msgid "Watch later"
msgstr "Guarda più tardi"
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by
# e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgstr "non è attivo"
msgid "{0} offline"
msgstr "{0} offline"
msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "Inserisci l'IP o l'URL del tuo Plex Media Server. Ad esempio:"
msgctxt "#39217"
msgid ""
"Does your Plex Media Server support SSL connections? (https instead of "
"http)?"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr ""
"Il tuo Plex Media Server supporta le connessioni SSL (https invece di http)?"
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1141,8 +1386,8 @@ msgid "plex.tv toggle successful"
msgstr "Login/Logout plex.tv eseguito"
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgstr "[COLOR yellow]Cercare ora fanart mancanti su FanartTV[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr "Cerca ora per le fanart mancanti su FanartTV"
msgctxt "#39223"
msgid ""
@ -1163,18 +1408,33 @@ msgstr "Solo mancanti"
# Message in the PKC settings if user has not logged in to plex.tv
msgctxt "#39226"
msgid "Not logged in to plex.tv"
msgstr ""
msgstr "Autenticazione negata su plex.tv"
# Message in the PKC settings if user is logged in to plex.tv
msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr ""
msgstr "Autenticazione concessa su plex.tv"
# Message in the PKC settings to display the plex.tv username. Leave the colon
# :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgstr ""
msgid "Plex admin user"
msgstr "Utente di amministrazione Plex"
# Error message if user could not log in; the actual user name will be
# appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr "Autenticazione dell'utente fallita su plex.tv "
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr "Utente plex autenticato"
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr "Cambia utente Plex"
msgctxt "#39250"
msgid ""
@ -1248,12 +1508,11 @@ msgstr ""
msgctxt "#39403"
msgid ""
"Cancelling the database syncing process. Current Kodi version is "
"unsupported. Please verify your logs for more info."
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
"Annullamento della sincronizzazione del database. La versione corrente di "
"Kodi non è supportata. Per maggiori informazioni si prega di verificare i "
"log."
"La versione corrente di Kodi non è supportata da PKC. Per favore consulta il"
" forum Plex."
msgctxt "#39405"
msgid "Plex playlists/nodes refreshed"
@ -1263,10 +1522,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr "Aggiornamento playlist/nodi Plex fallito"
msgctxt "#39407"
msgid "Full library sync finished"
msgstr "Sincronizzazione completa libreria terminata"
msgctxt "#39408"
msgid ""
"Sync had to skip some items because they could not be processed. Kodi may be"
@ -1293,12 +1548,16 @@ msgstr "ERRORE nella sincronizzazione della libreria"
msgctxt "#39500"
msgid "On Deck"
msgstr ""
msgstr "In primo piano"
msgctxt "#39501"
msgid "Collections"
msgstr "Collezioni"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr "PKC On Desk (più veloce)"
msgctxt "#39600"
msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1337,7 +1596,7 @@ msgstr "Sfoglia cartelle"
# Addon Summary
msgctxt "#39703"
msgid "Native Integration of Plex into Kodi"
msgstr ""
msgstr "Integrazione nativa di Plex su Kodi"
# For use with addon.xml (PKC metadata for Kodi, e.g. description)
# Addon Description
@ -1348,43 +1607,46 @@ msgid ""
"stored in the Kodi video and music databases (as this plugin directly "
"changes them). Use at your own risk!"
msgstr ""
"Connetti Kodi al tuo Plex Media Server. Questo plugin assume che tu gestisca"
" tutti i video con Plex (e non con Kodi). Potresti perdere i dati dei film e"
" della musica già memorizzati nel database di Kodi (questo plugin modifica "
"direttamente il database stesso). Usa a tuo rischio e pericolo!"
# For use with addon.xml (PKC metadata for Kodi, e.g. description)
# Addon Disclaimer
msgctxt "#39705"
msgid "Use at your own risk"
msgstr ""
msgstr "Usa a tuo rischio e pericolo"
# If user gets prompted to choose between several subtitles. Leave the number
# one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgid "Don't burn-in any subtitle"
msgstr ""
# If user gets prompted to choose between several audio/subtitle tracks and
# language is unknown
msgctxt "#39707"
msgid "unknown"
msgstr ""
msgstr "sconosciuto"
# If user gets prompted to choose between several subtitles and Plex adds the
# "default" flag
msgctxt "#39708"
msgid "Default"
msgstr ""
msgstr "Default"
# If user gets prompted to choose between several subtitles and Plex adds the
# "forced" flag
msgctxt "#39709"
msgid "Forced"
msgstr ""
msgstr "Forzato"
# If user gets prompted to choose between several subtitles the subtitle
# cannot be downloaded (has no 'key' attribute from the PMS), the subtitle
# needs to be burned in
msgctxt "#39710"
msgid "burn-in"
msgstr ""
msgstr "masterizza"
# Dialog text if PKC detected a new Music library and Kodi needs to be
# restarted
@ -1393,26 +1655,28 @@ msgid ""
"New Plex music library detected. Sorry, but we need to restart Kodi now due "
"to the changes made."
msgstr ""
"Identificata nuova libreria musicale Plex. Necessario riavvio di Kodi a "
"causa delle modifiche effettuate."
# Shown during sync process
msgctxt "#39712"
msgid "downloaded"
msgstr ""
msgstr "scaricato"
# Shown during sync process
msgctxt "#39713"
msgid "processed"
msgstr ""
msgstr "Processato"
# Shown during sync process
msgctxt "#39714"
msgid "Sync"
msgstr ""
msgstr "Sync"
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgstr ""
msgid "Synching playlists"
msgstr "Sincronizzazione delle playlist"
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}!
@ -1421,12 +1685,16 @@ msgid ""
"Kodi cannot parse {0}. PKC will not function correctly. Please visit {1} and"
" correct your file!"
msgstr ""
"Kodi non può analizzare {0}. PKC non funzionerà correttamente. Controlla {1}"
" e correggi il tuo file!"
# Shown once on first installation to comply with the terms of use of
# themoviedb.org
msgctxt "#39717"
msgid "PKC uses free additional artwork from www.themoviedb.org. Many thanks!"
msgstr ""
"PKC utilizza servizi di immagini addizionali gratuiti provenienti da "
"www.themoviedb.org. Molte grazie!"
# Shown during very first PKC setup only
msgctxt "#39718"
@ -1434,8 +1702,12 @@ msgid ""
"Do you want to replace your custom user ratings with an indicator of how "
"many versions of a media item you posses?"
msgstr ""
"Vuoi sostituire la valutazione dei contenuti dell'utente con l'indicazione "
"di quante versioni del contenuto sono disponibili?"
# In PKC Settings under Sync
msgctxt "#39719"
msgid "Replace user ratings with number of media versions"
msgstr ""
"Sostituisci la valutazione contenuti con il numero delle versioni del "
"contenuto disponibili"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,18 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2017
# Michiel van Baak <michiel@vanbaak.info>, 2017
# Michiel van Baak <michiel@vanbaak.info>, 2019
# Panja0 <panja0@protonmail.com>, 2019
# Nick Corthals <corthals.nick@gmail.com>, 2019
# Rick van Soest <r.vansoest@gmail.com>, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: PlexKodiConnect\n"
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Michiel van Baak <michiel@vanbaak.info>, 2017\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Rick van Soest <r.vansoest@gmail.com>, 2019\n"
"Language-Team: Dutch (Netherlands) (https://www.transifex.com/croneter/teams/73837/nl_NL/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -23,7 +27,7 @@ msgstr "PlexKodiConnect"
msgctxt "#30000"
msgid "Server Address (IP)"
msgstr "Serveradres (IP)"
msgstr "Server adres (IP)"
msgctxt "#30001"
msgid "Searching for PMS"
@ -41,8 +45,15 @@ msgid ""
"Warning: Kodi setting \"Play next video automatically\" is enabled. This "
"could break PKC. Deactivate?"
msgstr ""
"Waarschuwing: De kodi setting 'Automatisch volgende video afspelen' is "
"actief. Dit kan voor problemen zorgen. Setting deactiveren?"
"Waarschuwing: De kodi instelling 'Automatisch volgende video afspelen' is "
"actief. Dit kan voor problemen zorgen. Instelling deactiveren?"
msgctxt "#30004"
msgid ""
"The Kodi webserver is needed for artwork caching. PKC already set a strong, "
"random password automatically if you haven't done so already. Please confirm"
" the next dialog that you want to enable the webserver now with Yes."
msgstr ""
msgctxt "#30005"
msgid "Username: "
@ -51,32 +62,32 @@ msgstr "Gebruikersnaam: "
# Sync notification displayed if there is still artwork to be cached to Kodi
msgctxt "#30006"
msgid "Caching %s Plex images"
msgstr ""
msgstr "Caching %s Plex afbeeldingen"
# Sync notification displayed if syncing of major artwork is done
msgctxt "#30007"
msgid "Plex image caching done"
msgstr ""
msgstr "Cachen van Plex afbeeldingen afgerond"
# PKC settings artwork: Enable notifications for artwork image sync
msgctxt "#30008"
msgid "Enable notifications for image caching"
msgstr ""
msgstr "Activeer notificaties voor afbeelding caching"
# PKC settings artwork: Enable image caching during Kodi playback
msgctxt "#30009"
msgid "Enable image caching during Kodi playback (restart Kodi!)"
msgstr ""
msgstr "Afbeelding caching inschakelen tijdens Kodi weergave (herstart Kodi!)"
# PKC settings - Artwork
msgctxt "#30010"
msgid "Approximate progress"
msgstr ""
msgstr "Geschatte voortgang"
# PKC settings - Artwork
msgctxt "#30011"
msgid "Plex artwork (posters and backgrounds) left to cache:"
msgstr ""
msgid "Artwork left to cache:"
msgstr "Artwork nog te cachen:"
# Button text
msgctxt "#30012"
@ -92,10 +103,10 @@ msgctxt "#30014"
msgid "Connection"
msgstr "Verbinding"
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgstr ""
msgid "Fanart download already running"
msgstr "Het downloaden van fanart is al bezig"
msgctxt "#30016"
msgid "Device Name"
@ -109,18 +120,33 @@ msgstr "Onbevoegd voor PMS"
# Sync notification displayed for the number of fanart.tv lookups left
msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr ""
msgstr "FanartTV controleren op %s items"
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr ""
msgstr "FanartTV zoek actie voltooid"
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr "Synchroniseer Plex afspeellijsten (herstart Kodi!)"
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr "Synchroniseer alleen specifieke Plex afspeellijsten met Kodi"
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr "Geavanceerd"
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr "Synchroniseer alleen specifieke Kodi afspeellijsten met Plex"
msgctxt "#30024"
msgid "Username"
msgstr "Gebruikersnaam"
@ -129,6 +155,29 @@ msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr "Bericht weergeven als PMS offline gaat"
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr "Voorvoegsel in Plex afspeellijst naam om synchronisatie te activeren"
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr "Voorvoegsel in Kodi afspeellijst naam om synchronisatie te activeren"
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "PKC afbeelding caching voltooid"
# Warning shown when PKC switches to the Kodi default skin Estuary
msgctxt "#30029"
msgid ""
"To ensure a smooth PlexKodiConnect experience, it is HIGHLY recommended to "
"use Kodi's default skin \"Estuary\" for initial set-up and for possible "
"database resets. Continue?"
msgstr ""
msgctxt "#30030"
msgid "Port Number"
msgstr "Poortnummer"
@ -228,6 +277,10 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr "Videokwaliteit als Transcoden nodig is"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
msgctxt "#30165"
msgid "Direct Play"
msgstr "Direct Play"
@ -284,6 +337,46 @@ msgctxt "#30198"
msgid "Search"
msgstr "Zoek"
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid ""
"In the following window, enter the server's hostname (or IP) where your Plex"
" media resides. Mind the case!"
msgstr ""
"Voer in het volgende venster de hostnaam (of IP) van de server in waar uw "
"Plex media zich bevindt. Let op: hoofdlettergevoelig!"
# For setting up direct paths and adding network credentials - input window
# for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr "Voer de hostnaam van de server in (of IP)"
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid ""
"In the following window, enter the network protocol you would like to use. "
"This is likely 'smb'."
msgstr ""
"Voer in het volgende venster het netwerkprotocol in dat u wilt gebruiken. "
"Dit is waarschijnlijk 'smb'."
# For setting up direct paths and adding network credentials - input window
# protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr "Voer het netwerkprotocol in"
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr "De hostnaam of IP '{0}' die u heeft ingevoerd is niet geldig."
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr "Het protocol '{0}' dat u hebt ingevoerd wordt niet ondersteund."
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -397,6 +490,14 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr "Instellingen voor de Plex Server"
msgctxt "#30417"
msgid ""
"Could not change the Kodi settings file {0}. PKC might not work correctly. "
"Error: {1}"
msgstr ""
"Kon het Kodi instellingen bestand niet wijzigen {0}. PKC werkt mogelijk niet"
" correct. Fout: {1}"
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -407,10 +508,16 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr "SSL-clientcertificaat"
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
msgstr "[COLOR yellow]Reset inlog pogingen[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr "Synchroniseer Plex artwork van de PMS (aanbevolen)"
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr ""
"Validatie SSL certificaat niet gelukt. Controleer {0} voor oplossingen."
# PKC Settings, category name
msgctxt "#30506"
@ -444,13 +551,18 @@ msgstr "Afspeelmodus"
# PKC Settings - Artwork
msgctxt "#30512"
msgid "Cache all artwork for a smooth Kodi experience"
msgstr ""
msgstr "Sla alle artwork op in de cache zodat Kodi sneller reageert"
# PKC Settings - Artwork
msgctxt "#30513"
msgid "Limit artwork cache threads (recommended for rpi)"
msgstr "Beperk artwork cache threads (aanbevolen voor rpi)"
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr "Toon alle Plex extra's in plaats van meteen trailers af te spelen"
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -463,8 +575,8 @@ msgstr "Afspelen"
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgstr "[COLOR yellow]Geef netwerk authenticatie gegevens[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr "Stel netwerk inloggegevens in voor directe paden en directe weergave"
# PKC Settings - Playback
msgctxt "#30518"
@ -492,6 +604,23 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "Forceer transcoden h265/HEVC"
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr ""
"Toon ook de voortgang van de synchronisatie voor weergave status en "
"gebruikersgegevens"
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Selecteer Plex-bibliotheken om te synchroniseren"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -525,7 +654,7 @@ msgstr "Serverberichten"
# PKC Settings - Advanced
msgctxt "#30535"
msgid "Generate a new unique Plex device Id (e.g. to clone Kodi)"
msgstr ""
msgstr "Genereer een nieuwe unieke Plex apparaat-id (b.v. om Kodi te klonen)"
# PKC Settings - Connection
msgctxt "#30536"
@ -541,6 +670,8 @@ msgstr "BIJ AANPASSINGEN KODI HERSTARTEN"
msgctxt "#30538"
msgid "Manual complete reset of Kodi database necessary, see \"Advanced\""
msgstr ""
"Handmatige volledige reset van de Kodi database noodzakelijk, zie "
"\"Geavanceerd\""
# PKC Settings - Artwork
msgctxt "#30539"
@ -554,14 +685,19 @@ msgstr "Download film set/collectie artwork van FanArtTV"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Niet vragen om een bepaalde stream/kwaliteit te kiezen"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr "Kies altijd beste kwaliteit voor trailers"
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr "Prefereer Kodi artwork voor verzamelingen/sets"
msgctxt "#30544"
msgid "Artwork"
msgstr "Illustraties"
@ -571,6 +707,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Forceer transcoden van foto's"
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr ""
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr ""
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr ""
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -591,14 +742,30 @@ msgctxt "#33003"
msgid "Server is online"
msgstr "Server is online"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr ""
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr ""
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "Ongeldige gebruikersnaam of wachtwoord"
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgstr "Te vaak niet kunnen inloggen. Pas instellingen aan."
msgid "User is unauthorized for server {0}"
msgstr "Gebruiker is niet geautoriseerd voor server {0}"
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr ""
"Plex.tv heeft ons geen geldige lijst van Plex gebruikers verstrekt, sorry."
# Dialog before playback
msgctxt "#33013"
@ -654,7 +821,7 @@ msgstr "Ondertitel grootte"
# PKC Settings - Sync
msgctxt "#39003"
msgid "Number of simultaneous download threads"
msgstr ""
msgstr "Aantal gelijktijdige download threads"
# PKC Settings - Plex
msgctxt "#39004"
@ -727,18 +894,17 @@ msgstr "PKC plugin instellingen aanpassen? Kodi moet hierna herstart worden!"
# PKC Settings - Advanced
msgctxt "#39018"
msgid "Repair the Kodi database (force update all content)"
msgstr ""
msgstr "Herstel de Kodi database (forceer update alle inhoud)"
# PKC Settings - Advanced
msgctxt "#39019"
msgid "Reset the Kodi database and optionally reset PlexKodiConnect"
msgstr ""
msgstr "Reset de Kodi database en reset optioneel PlexKodiConnect"
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgstr ""
"[COLOR yellow]Alle afbeeldingen van Kodi nu aan de cache toevoegen[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr "Cache nu alle plaatjes in Kodi texture cache"
# Appended to a listed PMS if it is in the same LAN network as PKC
msgctxt "#39022"
@ -821,6 +987,16 @@ msgstr ""
"Vervang plex paden /volume1/media of \\\\myserver\\media met aangepaste SMB "
"paden smb://NAS/mystuff"
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr "Pas speciale tekens aan in pad (b.v. spatie naar %20)"
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -897,8 +1073,8 @@ msgstr "Niets werkt? Probeer een volledige reset!"
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgstr "[COLOR yellow]Kies Plex Server uit lijst[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr "Kies Plex server van een lijst"
# PKC Settings - Sync
msgctxt "#39051"
@ -927,19 +1103,15 @@ msgstr "Plex Server zoeken"
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgstr "Gebruikt door Sync en bij Direct Play"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
# PKC Settings, category name
msgctxt "#39057"
msgid "Customize Paths"
msgstr "Aanpassen van paden"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "On Deck van TV-series laat alle series zien"
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -979,12 +1151,8 @@ msgstr "Forceer Kodi thema reset bij afspelen stoppen"
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr ""
"Onlangs Toegevoegd: Toon ook al bekeken films (Ververs Plex "
"afspeellijst/nodes!)"
msgid "Recently Added: Also show already watched movies"
msgstr "Pas toegevoegd: Toon tevens de reeds bekeken films."
# PKC Settings - Connection
msgctxt "#39067"
@ -993,8 +1161,8 @@ msgstr "Huidige Plex Media Server:"
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgstr "[COLOR yellow]Handmatig Plex Media Server adres invoeren[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr "Voer handmatig het Plex Media Server adres in"
# PKC Settings - Connection
msgctxt "#39069"
@ -1011,6 +1179,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Huidige status van de plex.tv:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1021,10 +1194,10 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "TV series"
# PKC Settings - Playback
# PKC Settings - Sync
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgstr "Gebruik indien mogelijk altijd standaard Plex ondertitels"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
@ -1038,8 +1211,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgstr "Aantal PMS items in widgets laten zien (bijv. \"On Deck\")"
msgid "Maximum number of videos to show in widgets"
msgstr "Maximale aantal video's getoond in widgets"
# PKC Settings - Plex
msgctxt "#39078"
@ -1055,6 +1228,70 @@ msgstr ""
"Plex Companion kon de GDM poort niet gebruiken. Wijzig deze in de PKC "
"instellingen."
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
# identical!
msgctxt "#39080"
msgid ""
"Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if "
"you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
"Gebruik add-on paden (standaard, eenvoudig) of directe paden? Kies add-on "
"paden als u niet zeker bent. PKC werkt niet als je instellingen voor directe"
" paden verkeerd zijn!"
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr "Add-on paden"
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr "Directe paden"
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr "Kies PMS IP of URL"
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr "Kies PMS poort"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
"Herlaad de Kodi node bestanden om alles onderstaande instellingen door te "
"voeren"
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr ""
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr "Log-out Plex Home gebruiker "
@ -1063,10 +1300,6 @@ msgctxt "#39201"
msgid "Settings"
msgstr "Instellingen"
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr "Vernieuwen van Plex afspeellijsten/nodes"
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr "Handmatige sync uitvoeren"
@ -1094,8 +1327,8 @@ msgstr "PKC reset niet gelukt. Herstart Kodi."
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgstr "[COLOR yellow]Verander plex.tv login (aanmelden of afmelden)[/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr "Pas plex.tv login aan (aanmelden of afmelden)"
msgctxt "#39210"
msgid "Not yet connected to Plex Server"
@ -1105,22 +1338,19 @@ msgctxt "#39211"
msgid "Watch later"
msgstr "Later bekijken"
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by
# e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgstr "is offline"
msgid "{0} offline"
msgstr "{0} offline"
msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "Voer Plex Media Server adres in. Voorbeelden zijn:"
msgctxt "#39217"
msgid ""
"Does your Plex Media Server support SSL connections? (https instead of "
"http)?"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr ""
"Ondersteunt uw Plex Media Server SSL-verbindingen? (https in plaats van "
"http)?"
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1140,8 +1370,8 @@ msgid "plex.tv toggle successful"
msgstr "plex.tv aanpassing succesvol"
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgstr "[COLOR yellow]Nu zoeken naar missende artwork op FanartTV[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr "Zoek nu naar missende fanart op FanartTV"
msgctxt "#39223"
msgid ""
@ -1162,18 +1392,33 @@ msgstr "Alleen ontbrekende"
# Message in the PKC settings if user has not logged in to plex.tv
msgctxt "#39226"
msgid "Not logged in to plex.tv"
msgstr "Niet ingelogd op plek.tv"
msgstr "Niet ingelogd op plex.tv"
# Message in the PKC settings if user is logged in to plex.tv
msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr "Ingelogd op plek.tv"
msgstr "Ingelogd op plex.tv"
# Message in the PKC settings to display the plex.tv username. Leave the colon
# :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgstr "Plex gebruiker:"
msgid "Plex admin user"
msgstr "Plex admin gebruiker"
# Error message if user could not log in; the actual user name will be
# appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr "Aanmelden met plex.tv mislukt voor gebruiker"
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr "Ingelogde Plex gebruiker"
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr "Wijzig ingelogde Plex gebruiker"
msgctxt "#39250"
msgid ""
@ -1243,11 +1488,11 @@ msgstr ""
msgctxt "#39403"
msgid ""
"Cancelling the database syncing process. Current Kodi version is "
"unsupported. Please verify your logs for more info."
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
"Database synchroniseer proces geannuleerd. Huidige Kodi versie wordt niet "
"ondersteund. Controleer uw logbestanden voor meer info."
"De huidige Kodi versie wordt niet ondersteund door PKC. Raadpleeg het Plex "
"forum."
msgctxt "#39405"
msgid "Plex playlists/nodes refreshed"
@ -1257,10 +1502,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr "Plex afspeellijsten/nodes vernieuwen mislukt"
msgctxt "#39407"
msgid "Full library sync finished"
msgstr "Volledige bibliotheek synchronisatie voltooid"
msgctxt "#39408"
msgid ""
"Sync had to skip some items because they could not be processed. Kodi may be"
@ -1290,6 +1531,10 @@ msgctxt "#39501"
msgid "Collections"
msgstr "Verzamelingen"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr "PKC On Deck (sneller)"
msgctxt "#39600"
msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1349,11 +1594,10 @@ msgctxt "#39705"
msgid "Use at your own risk"
msgstr "Gebruik op eigen risico"
# If user gets prompted to choose between several subtitles. Leave the number
# one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgstr "1 Geen ondertiteling"
msgid "Don't burn-in any subtitle"
msgstr ""
# If user gets prompted to choose between several audio/subtitle tracks and
# language is unknown
@ -1407,8 +1651,8 @@ msgstr "Sync"
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgstr "items"
msgid "Synching playlists"
msgstr "Afspeellijsten synchroniseren"
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}!

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,15 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2017
# Daniel Leite <danieldefreitasleite@gmail.com>, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: PlexKodiConnect\n"
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2017\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Daniel Leite <danieldefreitasleite@gmail.com>, 2019\n"
"Language-Team: Portuguese (Brazil) (https://www.transifex.com/croneter/teams/73837/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -26,7 +28,7 @@ msgstr "Endereço do servidor (IP)"
msgctxt "#30001"
msgid "Searching for PMS"
msgstr ""
msgstr "Buscando PMS"
msgctxt "#30002"
msgid "Preferred playback method"
@ -40,6 +42,15 @@ msgid ""
"Warning: Kodi setting \"Play next video automatically\" is enabled. This "
"could break PKC. Deactivate?"
msgstr ""
"Atenção: Configuração \"Iniciar próximo vídeo automaticamente\" está ativada"
" no Kodi. Isto pode travar o PKC. Desativar?"
msgctxt "#30004"
msgid ""
"The Kodi webserver is needed for artwork caching. PKC already set a strong, "
"random password automatically if you haven't done so already. Please confirm"
" the next dialog that you want to enable the webserver now with Yes."
msgstr ""
msgctxt "#30005"
msgid "Username: "
@ -48,32 +59,34 @@ msgstr "Utilizador: "
# Sync notification displayed if there is still artwork to be cached to Kodi
msgctxt "#30006"
msgid "Caching %s Plex images"
msgstr ""
msgstr "Armazenando %s imagens Plex"
# Sync notification displayed if syncing of major artwork is done
msgctxt "#30007"
msgid "Plex image caching done"
msgstr ""
msgstr "Armazenamento de imagens Plex finalizado"
# PKC settings artwork: Enable notifications for artwork image sync
msgctxt "#30008"
msgid "Enable notifications for image caching"
msgstr ""
msgstr "Ativar notificações para armazenamento de imagens"
# PKC settings artwork: Enable image caching during Kodi playback
msgctxt "#30009"
msgid "Enable image caching during Kodi playback (restart Kodi!)"
msgstr ""
"Ativar armazenamento de imagens durante reprodução de media no Kodi "
"(necessário reiniciar o Kodi!)"
# PKC settings - Artwork
msgctxt "#30010"
msgid "Approximate progress"
msgstr ""
msgstr "Progresso aproximado"
# PKC settings - Artwork
msgctxt "#30011"
msgid "Plex artwork (posters and backgrounds) left to cache:"
msgstr ""
msgid "Artwork left to cache:"
msgstr "Artwork pendente de armazenamento: "
# Button text
msgctxt "#30012"
@ -89,10 +102,10 @@ msgctxt "#30014"
msgid "Connection"
msgstr "Ligação"
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgstr ""
msgid "Fanart download already running"
msgstr "Download de Fanart já em andamento"
msgctxt "#30016"
msgid "Device Name"
@ -106,18 +119,33 @@ msgstr "Não autorizado para o PMS"
# Sync notification displayed for the number of fanart.tv lookups left
msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr ""
msgstr "Verificando em FanartTV %s item(s)"
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr ""
msgstr "Verificação em FanartTV finalizado"
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr "Sincronizar playlists do Plex (necessário reiniciar o Kodi!)"
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr "Sincronizar somente playlists especificas do Plex para o Kodi"
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr "Avançado"
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr "Sincronizar somente playlists especificas do Kodi para o Plex"
msgctxt "#30024"
msgid "Username"
msgstr "Nome de Utilizador"
@ -126,6 +154,29 @@ msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr "Exibir mensagem se o PMS ficar offline"
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr "Prefixo do nome da playlist do Plex para ativar a sincronização"
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr "Prefixo do nome da playlist do Kodi para ativar a sincronização"
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "Armazenamento PKC somente imagens finalizado"
# Warning shown when PKC switches to the Kodi default skin Estuary
msgctxt "#30029"
msgid ""
"To ensure a smooth PlexKodiConnect experience, it is HIGHLY recommended to "
"use Kodi's default skin \"Estuary\" for initial set-up and for possible "
"database resets. Continue?"
msgstr ""
msgctxt "#30030"
msgid "Port Number"
msgstr "Número da Porta"
@ -137,7 +188,7 @@ msgstr "Eu possuo este Servidor Plex Media"
# Kodi context menu entry for movie and episode information screen
msgctxt "#30032"
msgid "Information"
msgstr ""
msgstr "Informação"
msgctxt "#30042"
msgid "Refresh"
@ -225,6 +276,10 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr "Qualidade de vídeo se a transcodificação for necessária"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
msgctxt "#30165"
msgid "Direct Play"
msgstr "Reprodução Direta"
@ -281,6 +336,42 @@ msgctxt "#30198"
msgid "Search"
msgstr "Procurar"
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid ""
"In the following window, enter the server's hostname (or IP) where your Plex"
" media resides. Mind the case!"
msgstr ""
# For setting up direct paths and adding network credentials - input window
# for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid ""
"In the following window, enter the network protocol you would like to use. "
"This is likely 'smb'."
msgstr ""
# For setting up direct paths and adding network credentials - input window
# protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr ""
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -394,6 +485,12 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr "Configurações do Servidor Plex"
msgctxt "#30417"
msgid ""
"Could not change the Kodi settings file {0}. PKC might not work correctly. "
"Error: {1}"
msgstr ""
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -404,10 +501,15 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr "Certificado SSL do cliente"
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
msgstr "[COLOR yellow]Repor tentativas de inicio de sessão[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr ""
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr ""
# PKC Settings, category name
msgctxt "#30506"
@ -448,6 +550,11 @@ msgctxt "#30513"
msgid "Limit artwork cache threads (recommended for rpi)"
msgstr "Limitar operações de cache das ilustrações (recomendado para rpi)"
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr ""
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -460,8 +567,8 @@ msgstr "Reproduzir"
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgstr "[COLOR yellow]Digite as credenciais da rede[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr ""
# PKC Settings - Playback
msgctxt "#30518"
@ -488,6 +595,21 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "Forçar a transcodificação dos codecs h265/HEVC"
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr ""
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr ""
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -550,14 +672,19 @@ msgstr "Descarregar arte para o conjunto/coleção de filmes da FanArtTV "
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Não perguntar para escolher uma certa qualidade/transmissão"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr "Escolher sempre a melhor qualidade para trailers"
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr ""
msgctxt "#30544"
msgid "Artwork"
msgstr "Ilustrações"
@ -567,6 +694,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Forçar transcodificação de imagens"
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr ""
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr ""
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr ""
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -587,14 +729,29 @@ msgctxt "#33003"
msgid "Server is online"
msgstr "Servidor está on-line"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr ""
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr ""
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "Utilizador ou palavra-passe inválidos"
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgstr "Falha na autenticação demasiadas vezes. Repor nas definições"
msgid "User is unauthorized for server {0}"
msgstr ""
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr ""
# Dialog before playback
msgctxt "#33013"
@ -737,9 +894,8 @@ msgstr ""
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr ""
"[COLOR yellow]Cache todas as imagens para o cache de textura do Kodi[/COLOR]"
# Appended to a listed PMS if it is in the same LAN network as PKC
msgctxt "#39022"
@ -819,6 +975,16 @@ msgstr ""
"Substituir caminhos Plex /volume1/media or \\\\myserver\\media with custom "
"SMB paths smb://NAS/mystuff"
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -895,8 +1061,8 @@ msgstr "Nada funciona? Tente uma reposição completa!"
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgstr "[COLOR yellow]Escolha o Servidor Plex de uma lista[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr ""
# PKC Settings - Sync
msgctxt "#39051"
@ -925,20 +1091,15 @@ msgstr "A procurar o(s) servidor(es) Plex"
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgstr "Usado pela sincronização e quando tentando Reprodução Direta"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
# PKC Settings, category name
msgctxt "#39057"
msgid "Customize Paths"
msgstr "Personalizar Caminhos"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr ""
"Extender Plex Séries de TV na vista \"Na Plataforma\" a todos os programas"
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -979,12 +1140,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgid "Recently Added: Also show already watched movies"
msgstr ""
"Adicionado Recentemente: Mostrar também filmes visualizados (Actualize "
"listas de reprodução/nós do Plex!)"
# PKC Settings - Connection
msgctxt "#39067"
@ -993,10 +1150,8 @@ msgstr "O seu Servidor Plex Media atual:"
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr ""
"[COLOR yellow]Inserir manualmente o endereço do Servidor Plex Media "
"Server[/COLOR]"
# PKC Settings - Connection
msgctxt "#39069"
@ -1013,6 +1168,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Estado atual da plex.tv:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1023,10 +1183,10 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Programas de TV"
# PKC Settings - Playback
# PKC Settings - Sync
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgstr "Utilize sempre as legendas Plex se possível"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
@ -1039,9 +1199,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgid "Maximum number of videos to show in widgets"
msgstr ""
"Numero de items do PMS a aparecer nas aplicacções (e.x. \"Na plataforma\")"
# PKC Settings - Plex
msgctxt "#39078"
@ -1057,6 +1216,65 @@ msgstr ""
"O Acompanhante Plex não conseguiu abrir a porta GDM. Por favor mude-a nas "
"definiçoes PKC."
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
# identical!
msgctxt "#39080"
msgid ""
"Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if "
"you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr ""
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr ""
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr ""
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr ""
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr "Sair da sessão do Utilizador Caseiro Plex"
@ -1065,10 +1283,6 @@ msgctxt "#39201"
msgid "Settings"
msgstr "Configurações"
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr "Atualizar listas de reprodução/nós do Plex"
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr "Executar sincronização manual da biblioteca"
@ -1098,9 +1312,8 @@ msgstr "Falha na reposição do PKC. Tente reiniciar o Kodi."
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr ""
"[COLOR yellow] Alternar inicio de sessão plex.tv (inicio ou saida)[/COLOR]"
msgctxt "#39210"
msgid "Not yet connected to Plex Server"
@ -1110,21 +1323,19 @@ msgctxt "#39211"
msgid "Watch later"
msgstr "Ver mais tarde"
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by
# e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgstr "está offline"
msgid "{0} offline"
msgstr ""
msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "Insira o seu IP ou URL do Servidor Plex Media, Exemplos são:"
msgctxt "#39217"
msgid ""
"Does your Plex Media Server support SSL connections? (https instead of "
"http)?"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr ""
"O seu Servidor Plex Media suporta conexões SSL? (https em vez de http)"
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1144,8 +1355,8 @@ msgid "plex.tv toggle successful"
msgstr "alternação da plex.tv bem sucedida "
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgstr "[COLOR yellow]Procurar ilustrações em falha na FanartTV now[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr ""
msgctxt "#39223"
msgid ""
@ -1173,11 +1384,26 @@ msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr "Iniciado na sessão da plex.tv"
# Message in the PKC settings to display the plex.tv username. Leave the colon
# :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgstr "Utilizador do Plex:"
msgid "Plex admin user"
msgstr ""
# Error message if user could not log in; the actual user name will be
# appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr ""
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr ""
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr ""
msgctxt "#39250"
msgid ""
@ -1249,12 +1475,9 @@ msgstr "poderá não funcionar corretamente até a base de dados ser reposta."
msgctxt "#39403"
msgid ""
"Cancelling the database syncing process. Current Kodi version is "
"unsupported. Please verify your logs for more info."
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
"A cancelar o processo de sincronização da base de dados. A versão corrente "
"do Kodi não é suportada. Por favor verifique os seus registos para mais "
"informações."
msgctxt "#39405"
msgid "Plex playlists/nodes refreshed"
@ -1264,10 +1487,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr "Atualização das listas de reprodução/nós do Plex falharam"
msgctxt "#39407"
msgid "Full library sync finished"
msgstr "Sincronização completa da biblioteca terminada"
msgctxt "#39408"
msgid ""
"Sync had to skip some items because they could not be processed. Kodi may be"
@ -1299,6 +1518,10 @@ msgctxt "#39501"
msgid "Collections"
msgstr "Coleções"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600"
msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1359,11 +1582,10 @@ msgctxt "#39705"
msgid "Use at your own risk"
msgstr "Use por risco de conta própria"
# If user gets prompted to choose between several subtitles. Leave the number
# one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgstr "1 Sem legendas"
msgid "Don't burn-in any subtitle"
msgstr ""
# If user gets prompted to choose between several audio/subtitle tracks and
# language is unknown
@ -1417,8 +1639,8 @@ msgstr "Sincronizar "
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgstr "items"
msgid "Synching playlists"
msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1,15 +1,16 @@
# XBMC Media Center language file
# Translators:
# Bruno Guerreiro <american.jesus.pt@gmail.com>, 2017
# Croneter None <croneter@gmail.com>, 2017
# Goncalo Campos <gda_campos@hotmail.com>, 2017
# Goncalo Campos <gda_campos@hotmail.com>, 2018
# Bruno Guerreiro <american.jesus.pt@gmail.com>, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: PlexKodiConnect\n"
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Goncalo Campos <gda_campos@hotmail.com>, 2017\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Bruno Guerreiro <american.jesus.pt@gmail.com>, 2019\n"
"Language-Team: Portuguese (Portugal) (https://www.transifex.com/croneter/teams/73837/pt_PT/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -28,7 +29,7 @@ msgstr "Endereço do servidor (IP)"
msgctxt "#30001"
msgid "Searching for PMS"
msgstr ""
msgstr "A procurar por PMS"
msgctxt "#30002"
msgid "Preferred playback method"
@ -43,6 +44,13 @@ msgid ""
"could break PKC. Deactivate?"
msgstr ""
msgctxt "#30004"
msgid ""
"The Kodi webserver is needed for artwork caching. PKC already set a strong, "
"random password automatically if you haven't done so already. Please confirm"
" the next dialog that you want to enable the webserver now with Yes."
msgstr ""
msgctxt "#30005"
msgid "Username: "
msgstr "Utilizador: "
@ -50,17 +58,17 @@ msgstr "Utilizador: "
# Sync notification displayed if there is still artwork to be cached to Kodi
msgctxt "#30006"
msgid "Caching %s Plex images"
msgstr ""
msgstr "A recolher %s imagens do Plex"
# Sync notification displayed if syncing of major artwork is done
msgctxt "#30007"
msgid "Plex image caching done"
msgstr ""
msgstr "Recolha de imagens do Plex concluída"
# PKC settings artwork: Enable notifications for artwork image sync
msgctxt "#30008"
msgid "Enable notifications for image caching"
msgstr ""
msgstr "Ativar notificações de recolha de imagens"
# PKC settings artwork: Enable image caching during Kodi playback
msgctxt "#30009"
@ -70,12 +78,12 @@ msgstr ""
# PKC settings - Artwork
msgctxt "#30010"
msgid "Approximate progress"
msgstr ""
msgstr "Progresso aproximado"
# PKC settings - Artwork
msgctxt "#30011"
msgid "Plex artwork (posters and backgrounds) left to cache:"
msgstr ""
msgid "Artwork left to cache:"
msgstr "Ilustrações para recolha em falta:"
# Button text
msgctxt "#30012"
@ -91,10 +99,10 @@ msgctxt "#30014"
msgid "Connection"
msgstr "Ligação"
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgstr ""
msgid "Fanart download already running"
msgstr "Recolha de ilustrações já em progresso"
msgctxt "#30016"
msgid "Device Name"
@ -108,18 +116,35 @@ msgstr "Não autorizado para o PMS"
# Sync notification displayed for the number of fanart.tv lookups left
msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr ""
msgstr "A pesquisar FanartTV por %s itens"
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr "Pesquisa de FanartTV concluída"
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr "Sincronizar de listas de reprodução do Plex (reiniciar Kodi)"
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr ""
"Apenas sincronizar listas de reprodução específicas do Plex para o Kodi"
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr "Avançado"
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr ""
"Apenas sincronizar listas de reprodução específicas do Kodi com o Plex"
msgctxt "#30024"
msgid "Username"
msgstr "Nome de Utilizador"
@ -128,6 +153,31 @@ msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr "Exibir mensagem se o PMS ficar offline"
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr ""
"Prefixo no nome da lista de reprodução do Plex para acionar a sincronização"
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr ""
"Prefixo no nome da lista de reprodução do Kodi para acionar a sincronização"
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr ""
# Warning shown when PKC switches to the Kodi default skin Estuary
msgctxt "#30029"
msgid ""
"To ensure a smooth PlexKodiConnect experience, it is HIGHLY recommended to "
"use Kodi's default skin \"Estuary\" for initial set-up and for possible "
"database resets. Continue?"
msgstr ""
msgctxt "#30030"
msgid "Port Number"
msgstr "Número da Porta"
@ -227,6 +277,10 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr "Qualidade de vídeo se a transcodificação for necessária"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
msgctxt "#30165"
msgid "Direct Play"
msgstr "Reprodução Direta"
@ -283,6 +337,44 @@ msgctxt "#30198"
msgid "Search"
msgstr "Procurar"
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid ""
"In the following window, enter the server's hostname (or IP) where your Plex"
" media resides. Mind the case!"
msgstr ""
"Na janela seguinte, insira o nome do host (ou IP) do servidor onde reside a "
"media do Plex. "
# For setting up direct paths and adding network credentials - input window
# for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr "Intrusa o nome do host do servidor (ou IP)"
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid ""
"In the following window, enter the network protocol you would like to use. "
"This is likely 'smb'."
msgstr ""
# For setting up direct paths and adding network credentials - input window
# protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr ""
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -396,6 +488,12 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr "Configurações do Servidor Plex"
msgctxt "#30417"
msgid ""
"Could not change the Kodi settings file {0}. PKC might not work correctly. "
"Error: {1}"
msgstr ""
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -406,10 +504,15 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr "Certificado SSL do cliente"
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
msgstr "[COLOR yellow]Repor tentativas de inicio de sessão[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr ""
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr ""
# PKC Settings, category name
msgctxt "#30506"
@ -450,6 +553,11 @@ msgctxt "#30513"
msgid "Limit artwork cache threads (recommended for rpi)"
msgstr "Limitar operações de cache das ilustrações (recomendado para rpi)"
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr ""
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -462,8 +570,8 @@ msgstr "Reproduzir"
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgstr "[COLOR yellow]Digite as credenciais da rede[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr ""
# PKC Settings - Playback
msgctxt "#30518"
@ -490,6 +598,21 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "Forçar a transcodificação dos codecs h265/HEVC"
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr ""
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr ""
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -552,14 +675,19 @@ msgstr "Descarregar arte para o conjunto/coleção de filmes da FanArtTV "
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Não perguntar para escolher uma certa qualidade/transmissão"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr "Escolher sempre a melhor qualidade para trailers"
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr ""
msgctxt "#30544"
msgid "Artwork"
msgstr "Ilustrações"
@ -569,6 +697,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Forçar transcodificação de imagens"
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr ""
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr ""
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr ""
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -589,14 +732,29 @@ msgctxt "#33003"
msgid "Server is online"
msgstr "Servidor está on-line"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr ""
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr ""
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "Utilizador ou palavra-passe inválidos"
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgstr "Falha na autenticação demasiadas vezes. Repor nas definições"
msgid "User is unauthorized for server {0}"
msgstr ""
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr ""
# Dialog before playback
msgctxt "#33013"
@ -739,9 +897,8 @@ msgstr ""
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr ""
"[COLOR yellow]Cache todas as imagens para o cache de textura do Kodi[/COLOR]"
# Appended to a listed PMS if it is in the same LAN network as PKC
msgctxt "#39022"
@ -821,6 +978,16 @@ msgstr ""
"Substituir caminhos Plex /volume1/media or \\\\myserver\\media with custom "
"SMB paths smb://NAS/mystuff"
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -897,8 +1064,8 @@ msgstr "Nada funciona? Tente uma reposição completa!"
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgstr "[COLOR yellow]Escolha o Servidor Plex de uma lista[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr ""
# PKC Settings - Sync
msgctxt "#39051"
@ -927,20 +1094,15 @@ msgstr "A procurar o(s) servidor(es) Plex"
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgstr "Usado pela sincronização e quando tentando Reprodução Direta"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
# PKC Settings, category name
msgctxt "#39057"
msgid "Customize Paths"
msgstr "Personalizar Caminhos"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr ""
"Extender Plex Séries de TV na vista \"Na Plataforma\" a todos os programas"
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -981,12 +1143,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgid "Recently Added: Also show already watched movies"
msgstr ""
"Adicionado Recentemente: Mostrar também filmes visualizados (Actualize "
"listas de reprodução/nós do Plex!)"
# PKC Settings - Connection
msgctxt "#39067"
@ -995,10 +1153,8 @@ msgstr "O seu Servidor Plex Media atual:"
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr ""
"[COLOR yellow]Inserir manualmente o endereço do Servidor Plex Media "
"Server[/COLOR]"
# PKC Settings - Connection
msgctxt "#39069"
@ -1015,6 +1171,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Estado atual da plex.tv:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1025,10 +1186,10 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Programas de TV"
# PKC Settings - Playback
# PKC Settings - Sync
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgstr "Utilize sempre as legendas Plex se possível"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
@ -1041,9 +1202,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgid "Maximum number of videos to show in widgets"
msgstr ""
"Numero de items do PMS a aparecer nas aplicacções (e.x. \"Na plataforma\")"
# PKC Settings - Plex
msgctxt "#39078"
@ -1059,6 +1219,65 @@ msgstr ""
"O Acompanhante Plex não conseguiu abrir a porta GDM. Por favor mude-a nas "
"definiçoes PKC."
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
# identical!
msgctxt "#39080"
msgid ""
"Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if "
"you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr ""
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr ""
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr ""
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr ""
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr "Sair da sessão do Utilizador Caseiro Plex"
@ -1067,10 +1286,6 @@ msgctxt "#39201"
msgid "Settings"
msgstr "Configurações"
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr "Atualizar listas de reprodução/nós do Plex"
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr "Executar sincronização manual da biblioteca"
@ -1100,9 +1315,8 @@ msgstr "Falha na reposição do PKC. Tente reiniciar o Kodi."
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr ""
"[COLOR yellow] Alternar inicio de sessão plex.tv (inicio ou saida)[/COLOR]"
msgctxt "#39210"
msgid "Not yet connected to Plex Server"
@ -1112,21 +1326,19 @@ msgctxt "#39211"
msgid "Watch later"
msgstr "Ver mais tarde"
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by
# e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgstr "está offline"
msgid "{0} offline"
msgstr ""
msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "Insira o seu IP ou URL do Servidor Plex Media, Exemplos são:"
msgctxt "#39217"
msgid ""
"Does your Plex Media Server support SSL connections? (https instead of "
"http)?"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr ""
"O seu Servidor Plex Media suporta conexões SSL? (https em vez de http)"
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1146,8 +1358,8 @@ msgid "plex.tv toggle successful"
msgstr "alternação da plex.tv bem sucedida "
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgstr "[COLOR yellow]Procurar ilustrações em falha na FanartTV now[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr ""
msgctxt "#39223"
msgid ""
@ -1175,11 +1387,26 @@ msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr "Iniciado na sessão da plex.tv"
# Message in the PKC settings to display the plex.tv username. Leave the colon
# :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgstr "Utilizador do Plex:"
msgid "Plex admin user"
msgstr ""
# Error message if user could not log in; the actual user name will be
# appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr ""
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr ""
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr ""
msgctxt "#39250"
msgid ""
@ -1251,12 +1478,9 @@ msgstr "poderá não funcionar corretamente até a base de dados ser reposta."
msgctxt "#39403"
msgid ""
"Cancelling the database syncing process. Current Kodi version is "
"unsupported. Please verify your logs for more info."
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
"A cancelar o processo de sincronização da base de dados. A versão corrente "
"do Kodi não é suportada. Por favor verifique os seus registos para mais "
"informações."
msgctxt "#39405"
msgid "Plex playlists/nodes refreshed"
@ -1266,10 +1490,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr "Atualização das listas de reprodução/nós do Plex falharam"
msgctxt "#39407"
msgid "Full library sync finished"
msgstr "Sincronização completa da biblioteca terminada"
msgctxt "#39408"
msgid ""
"Sync had to skip some items because they could not be processed. Kodi may be"
@ -1301,6 +1521,10 @@ msgctxt "#39501"
msgid "Collections"
msgstr "Coleções"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600"
msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1361,11 +1585,10 @@ msgctxt "#39705"
msgid "Use at your own risk"
msgstr "Use por risco de conta própria"
# If user gets prompted to choose between several subtitles. Leave the number
# one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgstr "1 Sem legendas"
msgid "Don't burn-in any subtitle"
msgstr ""
# If user gets prompted to choose between several audio/subtitle tracks and
# language is unknown
@ -1419,8 +1642,8 @@ msgstr "Sincronizar "
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgstr "items"
msgid "Synching playlists"
msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1,17 +1,19 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2017
# Vladimir Supranenok <stark_v@mail.ru>, 2017
# Alexey Korobcov <korobcoff@gmail.com>, 2017
# Павел Хоменко, 2017
# Алексей Коробцов <korobcoff@gmail.com>, 2017
# Alex Freit <alex.nx@mail.ru>, 2017
# Alex Freit <alex.nx@mail.ru>, 2019
# Vladimir Supranenok <stark_v@mail.ru>, 2019
# Vlad Anisimov <uniss@ua.fm>, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: PlexKodiConnect\n"
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Alex Freit <alex.nx@mail.ru>, 2017\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Vlad Anisimov <uniss@ua.fm>, 2019\n"
"Language-Team: Russian (Russia) (https://www.transifex.com/croneter/teams/73837/ru_RU/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -47,6 +49,13 @@ msgstr ""
"Предупреждение: включена настройка Kodi «Воспроизвести следующее видео "
"автоматически». Это может сломать PKC. Деактивировать?"
msgctxt "#30004"
msgid ""
"The Kodi webserver is needed for artwork caching. PKC already set a strong, "
"random password automatically if you haven't done so already. Please confirm"
" the next dialog that you want to enable the webserver now with Yes."
msgstr ""
msgctxt "#30005"
msgid "Username: "
msgstr "Имя пользователя: "
@ -54,32 +63,34 @@ msgstr "Имя пользователя: "
# Sync notification displayed if there is still artwork to be cached to Kodi
msgctxt "#30006"
msgid "Caching %s Plex images"
msgstr ""
msgstr "Кэширование %s изображений Plex"
# Sync notification displayed if syncing of major artwork is done
msgctxt "#30007"
msgid "Plex image caching done"
msgstr ""
msgstr "Кэширование изображений Plex завершено"
# PKC settings artwork: Enable notifications for artwork image sync
msgctxt "#30008"
msgid "Enable notifications for image caching"
msgstr ""
msgstr "Включить уведомления для кэширования изображений"
# PKC settings artwork: Enable image caching during Kodi playback
msgctxt "#30009"
msgid "Enable image caching during Kodi playback (restart Kodi!)"
msgstr ""
"Включить кэширование изображений во время воспроизведения Kodi "
"(перезапустите Kodi!)"
# PKC settings - Artwork
msgctxt "#30010"
msgid "Approximate progress"
msgstr ""
msgstr "Приблизительный прогресс"
# PKC settings - Artwork
msgctxt "#30011"
msgid "Plex artwork (posters and backgrounds) left to cache:"
msgstr ""
msgid "Artwork left to cache:"
msgstr "Оставить в кеше иллюстрации:"
# Button text
msgctxt "#30012"
@ -95,10 +106,10 @@ msgctxt "#30014"
msgid "Connection"
msgstr "Подключение"
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgstr ""
msgid "Fanart download already running"
msgstr "Fanart уже загружаются"
msgctxt "#30016"
msgid "Device Name"
@ -112,18 +123,33 @@ msgstr "Не авторизован на сервере Plex"
# Sync notification displayed for the number of fanart.tv lookups left
msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr ""
msgstr "FanartTV осталось проверить %s"
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr ""
msgstr "Поиск FanartTV завершен"
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr "Синхронизация плейлистов Plex (перезапустите Kodi!)"
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr "Синхронизировать только некоторые плейлисты из Plex"
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr "Расширенные"
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr "Синхронизировать только некоторые плейлисты из Kodi"
msgctxt "#30024"
msgid "Username"
msgstr "Имя пользователя"
@ -132,6 +158,29 @@ msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr "Сообщать об отключении от сервера Plex "
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr "Префикс плейлиста в Plex для синхронизации"
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr "Префикс плейлиста в Kodi для синхронизации"
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "Кеширование изображений PKC завершено"
# Warning shown when PKC switches to the Kodi default skin Estuary
msgctxt "#30029"
msgid ""
"To ensure a smooth PlexKodiConnect experience, it is HIGHLY recommended to "
"use Kodi's default skin \"Estuary\" for initial set-up and for possible "
"database resets. Continue?"
msgstr ""
msgctxt "#30030"
msgid "Port Number"
msgstr "Порт"
@ -231,6 +280,10 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr "Качество при транскодинге"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
msgctxt "#30165"
msgid "Direct Play"
msgstr "Прямое воспроизведение"
@ -287,6 +340,46 @@ msgctxt "#30198"
msgid "Search"
msgstr "Поиск"
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid ""
"In the following window, enter the server's hostname (or IP) where your Plex"
" media resides. Mind the case!"
msgstr ""
"В следующем окне введите сетевое имя сервера (или его IP-адрес) на котором "
"находятся медиафайлы. Соблюдайте регистр!"
# For setting up direct paths and adding network credentials - input window
# for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr "Введите имя сервера (или IP)"
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid ""
"In the following window, enter the network protocol you would like to use. "
"This is likely 'smb'."
msgstr ""
"В следующем окне введите сетевой протокол для использования. Например "
"\"smb\""
# For setting up direct paths and adding network credentials - input window
# protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr "Введите сетевой протокол"
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr "Введенное вами имя хоста или IP-адрес \"{0}\" не верно."
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr "Введенный вами протокол {0} не поддерживается."
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -398,6 +491,14 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr "Настройки для сервера Plex"
msgctxt "#30417"
msgid ""
"Could not change the Kodi settings file {0}. PKC might not work correctly. "
"Error: {1}"
msgstr ""
"Не удалось изменить файл настроек Kodi {0}. PKC может работать некорректно. "
"Ошибка: {1}"
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -408,10 +509,16 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr "SSL сертификат клиента"
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
msgstr "[COLOR yellow]Сбросить попытки входа[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr "Синхронизировать иллюстрации с Plex. (рекомендуется)"
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr ""
"Ошибка проверки SSL сертификата. Пожалуйста просмотрите {0} для решения"
# PKC Settings, category name
msgctxt "#30506"
@ -445,7 +552,7 @@ msgstr "Режим воспроизведения"
# PKC Settings - Artwork
msgctxt "#30512"
msgid "Cache all artwork for a smooth Kodi experience"
msgstr ""
msgstr "Кэш всех изображений для плавной работы Kodi"
# PKC Settings - Artwork
msgctxt "#30513"
@ -453,6 +560,13 @@ msgid "Limit artwork cache threads (recommended for rpi)"
msgstr ""
"Ограничить потоки кешированиa иллюстраций (рекомендуется для Raspberry)"
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr ""
"Воспроизвести все дополнительные материалы Plex, вместо того, чтобы сразу "
"воспроизводить трейлеры"
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -465,8 +579,8 @@ msgstr "Воспроизведение"
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgstr "[COLOR yellow]Введите сетевые учетные данные[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr "Ввести сетевые учетные данные для прямых путей и Direct play"
# PKC Settings - Playback
msgctxt "#30518"
@ -494,6 +608,23 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "Транскодировать h265/HEVC"
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr ""
"Также показать процесс синхронизации отметки воспроизведения и "
"пользовательских данных"
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Выбор библиотек Plex для синхронизации"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -528,6 +659,7 @@ msgstr "Сообщения сервера"
msgctxt "#30535"
msgid "Generate a new unique Plex device Id (e.g. to clone Kodi)"
msgstr ""
"Генерировать новый ID устройства Plex (например, при клонировании Kodi)"
# PKC Settings - Connection
msgctxt "#30536"
@ -537,12 +669,13 @@ msgstr "Пользователь должен входить при каждом
# PKC Settings warning
msgctxt "#30537"
msgid "RESTART KODI IF YOU MAKE ANY CHANGES"
msgstr "ПЕРЕЗАПУСТИТЕ KODI, ЕСЛИ ВНОСИЛИ КАКИЕ-ТО ИЗМЕНЕНИЯ"
msgstr "ПЕРЕЗАПУСТИТЕ KODI, ЕСЛИ ВНОСИЛИ ИЗМЕНЕНИЯ"
# PKC Settings warning
msgctxt "#30538"
msgid "Manual complete reset of Kodi database necessary, see \"Advanced\""
msgstr ""
"Необходим ручной полный сброс базы данных Kodi, см. Раздел \"Дополнительно\""
# PKC Settings - Artwork
msgctxt "#30539"
@ -556,14 +689,19 @@ msgstr "Загружать иллюстрации сборников с FanArtTV
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Не просить выбрать качество потока"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr "Всегда выбирать лучшее качество для трейлеров"
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr "Предпочитать иллюстрации из Kodi для коллекций"
msgctxt "#30544"
msgid "Artwork"
msgstr "Иллюстрации"
@ -573,6 +711,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Принудительно транскодировать изображения"
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr ""
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr ""
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr ""
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -593,14 +746,29 @@ msgctxt "#33003"
msgid "Server is online"
msgstr "Сервер в сети"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr "PMS принудительное транскодирование"
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr "PMS принудительное прямое вещание"
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "Неверное имя пользователя или пароль"
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgstr "Множественная ошибка авторизации. Сбросьте настройки."
msgid "User is unauthorized for server {0}"
msgstr "Пользователь не авторизован на сервере {0}"
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr "Plex.tv не предоставил списка пользователей Plex, извините"
# Dialog before playback
msgctxt "#33013"
@ -654,7 +822,7 @@ msgstr "Размер субтитров внедряемых в видео"
# PKC Settings - Sync
msgctxt "#39003"
msgid "Number of simultaneous download threads"
msgstr ""
msgstr "Количество одновременных тем для загрузки"
# PKC Settings - Plex
msgctxt "#39004"
@ -729,17 +897,18 @@ msgstr ""
msgctxt "#39018"
msgid "Repair the Kodi database (force update all content)"
msgstr ""
"Восстановление базы данных Kodi (принудительное обновление всего "
"содержимого)"
# PKC Settings - Advanced
msgctxt "#39019"
msgid "Reset the Kodi database and optionally reset PlexKodiConnect"
msgstr ""
msgstr "Сброс базы данных Kodi и возможная перезагрузка PlexKodiConnect"
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgstr ""
"[COLOR yellow]Кешировать все изображения сейчас в Kodi texture cache[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr "Кешировать все изображения в кеш Kodi"
# Appended to a listed PMS if it is in the same LAN network as PKC
msgctxt "#39022"
@ -822,6 +991,16 @@ msgstr ""
"Заменить пути Plex /volume1/media либо \\\\myserver\\media на "
"пользовательские Samba пути smb://NAS/mystuff"
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr "Преобразуйте специальные символы в пути. (например пробел в %20)"
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -897,8 +1076,8 @@ msgstr "Ничего не работает? Попробуйте общий сб
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgstr "[COLOR yellow]Выберите сервер Plex из списка[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr "Выбрать сервер Plex из списка"
# PKC Settings - Sync
msgctxt "#39051"
@ -927,19 +1106,17 @@ msgstr "Поиск сервера Plex"
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgstr "Используется при синхронизации и прямом воспроизведении"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
"Используется при синхронизации и прямом воспроизведении. Перезапустите Kodi "
"для применения изменений!"
# PKC Settings, category name
msgctxt "#39057"
msgid "Customize Paths"
msgstr "Изменить пути"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "В \"Текущем\" показывать все сериалы"
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -979,12 +1156,8 @@ msgstr "Принудительное обновление обложки Kodi п
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr ""
"Недавно добавлено: также показывать просмотренные фильмы (обновите "
"плейлисты/списки Plex)"
msgid "Recently Added: Also show already watched movies"
msgstr "Недавно добавлено: также показывать просмотренные фильмы"
# PKC Settings - Connection
msgctxt "#39067"
@ -993,8 +1166,8 @@ msgstr "Ваш текущий сервер Plex:"
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgstr "[COLOR yellow]Вручную ввести адрес сервера Plex[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr "Ввести адрес сервера Plex вручную"
# PKC Settings - Connection
msgctxt "#39069"
@ -1011,6 +1184,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Текущий статус на plex.tv:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1021,10 +1199,10 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Сериалы"
# PKC Settings - Playback
# PKC Settings - Sync
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgstr "Использовать субтитры по умолчанию из Plex, если доступны"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
@ -1038,13 +1216,13 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgstr "Количество элементов, отображаемых в виджетах (например \"Текущие\")"
msgid "Maximum number of videos to show in widgets"
msgstr "Максимальное количество видео для отображения в виджетах"
# PKC Settings - Plex
msgctxt "#39078"
msgid "Plex Companion Update Port (change only if needed)"
msgstr "Порт обновления Plex Companion (меняйте только если необходимо)"
msgstr "Порт обновления Plex Companion (если необходимо)"
# Error message
msgctxt "#39079"
@ -1054,6 +1232,68 @@ msgid ""
msgstr ""
"Plex Companion не может открыть порт GDM. Смените его в настройках PKC."
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
# identical!
msgctxt "#39080"
msgid ""
"Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if "
"you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
"Использовать пути к файлам дополнения (по умолчанию, легко) или прямые пути?"
" Если не уверены - выбирайте пути аддона. Помните что PKC не будет работать "
"если прямые пути настроены неверно."
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr "Пути дополнения"
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr "Прямые пути"
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr "Введите IP или адрес PMS"
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr "Введите порт PMS"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr "Перезаписать узлы БД Kodi, чтобы применить следующие настройки"
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr ""
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr "Выйти из Plex"
@ -1062,10 +1302,6 @@ msgctxt "#39201"
msgid "Settings"
msgstr "Настройки"
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr "Обновить плейлисты/списки Plex"
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr "Синхронизировать вручную"
@ -1094,9 +1330,8 @@ msgstr "Невозможно сбросить PKC. Попробуйте пере
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgstr ""
"[COLOR yellow]Переключить авторизацию plex.tv(войти или выйти)[/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr "Переключить статус plex.tv (войти или выйти)"
msgctxt "#39210"
msgid "Not yet connected to Plex Server"
@ -1106,20 +1341,19 @@ msgctxt "#39211"
msgid "Watch later"
msgstr "Смотреть позже"
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by
# e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgstr "Нет соединения"
msgid "{0} offline"
msgstr "{0} недоступен"
msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "Введите IP или URL Вашего Plex-сервера. Например:"
msgctxt "#39217"
msgid ""
"Does your Plex Media Server support SSL connections? (https instead of "
"http)?"
msgstr "Ваш Plex-сервер поддерживает SSL соединение (https вместо http)?"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr ""
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1139,8 +1373,8 @@ msgid "plex.tv toggle successful"
msgstr "переключение plex.tv удачно"
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgstr "[COLOR yellow]Найти отсутствующие иллюстрации на FanartTV[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr "Искать отсутствующие иллюстрации на FanartTV сейчас"
msgctxt "#39223"
msgid ""
@ -1168,11 +1402,26 @@ msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr "Вы вошли в plex.tv"
# Message in the PKC settings to display the plex.tv username. Leave the colon
# :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgstr "Пользователь Plex:"
msgid "Plex admin user"
msgstr "Администратор Plex"
# Error message if user could not log in; the actual user name will be
# appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr "Вход в plex.tv неудачен"
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr "Учетная запись Plex home"
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr "Изменить учетную запись Plex home"
msgctxt "#39250"
msgid ""
@ -1244,11 +1493,11 @@ msgstr "может работать неправильно до сброса б
msgctxt "#39403"
msgid ""
"Cancelling the database syncing process. Current Kodi version is "
"unsupported. Please verify your logs for more info."
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
"Синхронизация отменена, текущая версия Kodi не поддерживается. Больше "
"информации Вы найдёте в логах."
"Текущая версия Kodi не поддерживается PKC. Пожалуйста проконсультируйтесь на"
" форуме Plex"
msgctxt "#39405"
msgid "Plex playlists/nodes refreshed"
@ -1258,10 +1507,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr "Обновление плейлистов/списков Plex не удалось"
msgctxt "#39407"
msgid "Full library sync finished"
msgstr "Полная синхронизация библиотеки завершена"
msgctxt "#39408"
msgid ""
"Sync had to skip some items because they could not be processed. Kodi may be"
@ -1293,6 +1538,10 @@ msgctxt "#39501"
msgid "Collections"
msgstr "Коллекции"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr "PKC \"Текущие\" (быстрее)"
msgctxt "#39600"
msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1351,11 +1600,10 @@ msgctxt "#39705"
msgid "Use at your own risk"
msgstr "Используйте на свой страх и риск"
# If user gets prompted to choose between several subtitles. Leave the number
# one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgstr "1 Без субтитров"
msgid "Don't burn-in any subtitle"
msgstr ""
# If user gets prompted to choose between several audio/subtitle tracks and
# language is unknown
@ -1409,8 +1657,8 @@ msgstr "Синхронизация"
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgstr "элементов"
msgid "Synching playlists"
msgstr "Синхронизация плейлистов"
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}!

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,13 +2,15 @@
# Translators:
# Croneter None <croneter@gmail.com>, 2017
# Tony Z <zj45499@gmail.com>, 2017
# Jingen Chen <jingen.chen@gmail.com>, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: PlexKodiConnect\n"
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Tony Z <zj45499@gmail.com>, 2017\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Jingen Chen <jingen.chen@gmail.com>, 2019\n"
"Language-Team: Chinese (China) (https://www.transifex.com/croneter/teams/73837/zh_CN/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -27,7 +29,7 @@ msgstr "服务器地址(IP)"
msgctxt "#30001"
msgid "Searching for PMS"
msgstr ""
msgstr "正在搜索Plex媒体服务器"
msgctxt "#30002"
msgid "Preferred playback method"
@ -42,6 +44,13 @@ msgid ""
"could break PKC. Deactivate?"
msgstr ""
msgctxt "#30004"
msgid ""
"The Kodi webserver is needed for artwork caching. PKC already set a strong, "
"random password automatically if you haven't done so already. Please confirm"
" the next dialog that you want to enable the webserver now with Yes."
msgstr ""
msgctxt "#30005"
msgid "Username: "
msgstr "用户名 "
@ -49,22 +58,22 @@ msgstr "用户名 "
# Sync notification displayed if there is still artwork to be cached to Kodi
msgctxt "#30006"
msgid "Caching %s Plex images"
msgstr ""
msgstr "正在缓存Plex图片%s"
# Sync notification displayed if syncing of major artwork is done
msgctxt "#30007"
msgid "Plex image caching done"
msgstr ""
msgstr "Plex图片缓存完成"
# PKC settings artwork: Enable notifications for artwork image sync
msgctxt "#30008"
msgid "Enable notifications for image caching"
msgstr ""
msgstr "启用图片缓存通知"
# PKC settings artwork: Enable image caching during Kodi playback
msgctxt "#30009"
msgid "Enable image caching during Kodi playback (restart Kodi!)"
msgstr ""
msgstr "允许Kodi播放时缓存图片需重启Kodi"
# PKC settings - Artwork
msgctxt "#30010"
@ -73,7 +82,7 @@ msgstr ""
# PKC settings - Artwork
msgctxt "#30011"
msgid "Plex artwork (posters and backgrounds) left to cache:"
msgid "Artwork left to cache:"
msgstr ""
# Button text
@ -90,9 +99,9 @@ msgctxt "#30014"
msgid "Connection"
msgstr "连接"
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgid "Fanart download already running"
msgstr ""
msgctxt "#30016"
@ -109,16 +118,31 @@ msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr ""
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr ""
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr ""
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr ""
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr "高级"
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr ""
msgctxt "#30024"
msgid "Username"
msgstr "用户名"
@ -127,6 +151,29 @@ msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr "如Plex媒体服务器离线则显示消息"
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr ""
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr ""
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr ""
# Warning shown when PKC switches to the Kodi default skin Estuary
msgctxt "#30029"
msgid ""
"To ensure a smooth PlexKodiConnect experience, it is HIGHLY recommended to "
"use Kodi's default skin \"Estuary\" for initial set-up and for possible "
"database resets. Continue?"
msgstr ""
msgctxt "#30030"
msgid "Port Number"
msgstr "端口号"
@ -226,6 +273,10 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr "如须转码视频质量"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
msgctxt "#30165"
msgid "Direct Play"
msgstr "直接播放"
@ -282,6 +333,42 @@ msgctxt "#30198"
msgid "Search"
msgstr "搜索"
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid ""
"In the following window, enter the server's hostname (or IP) where your Plex"
" media resides. Mind the case!"
msgstr ""
# For setting up direct paths and adding network credentials - input window
# for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid ""
"In the following window, enter the network protocol you would like to use. "
"This is likely 'smb'."
msgstr ""
# For setting up direct paths and adding network credentials - input window
# protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr ""
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -393,6 +480,12 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr "Plex服务器配置"
msgctxt "#30417"
msgid ""
"Could not change the Kodi settings file {0}. PKC might not work correctly. "
"Error: {1}"
msgstr ""
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -403,10 +496,15 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr "客户端SSL证书"
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
msgstr "[黄色]重置登录尝试[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr ""
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr ""
# PKC Settings, category name
msgctxt "#30506"
@ -447,6 +545,11 @@ msgctxt "#30513"
msgid "Limit artwork cache threads (recommended for rpi)"
msgstr "限制插图缓存线程(rpi下推荐)"
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr ""
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -459,8 +562,8 @@ msgstr "回放"
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgstr "[黄色]输入网络凭证[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr ""
# PKC Settings - Playback
msgctxt "#30518"
@ -487,6 +590,21 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "强制h265/HEVC转码"
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr ""
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr ""
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -549,14 +667,19 @@ msgstr "从FanArtTV下载额外的电影集/收藏art"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "无需询问挑选特定的串流/质量"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr "总是为预告片挑选最佳质量"
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr ""
msgctxt "#30544"
msgid "Artwork"
msgstr "插图"
@ -566,6 +689,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "强制图片转码"
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr ""
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr ""
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr ""
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -586,14 +724,29 @@ msgctxt "#33003"
msgid "Server is online"
msgstr "服务器在线"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr ""
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr ""
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "无效的用户名或密码"
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgstr "验证失败次数过多。在设置中重置。"
msgid "User is unauthorized for server {0}"
msgstr ""
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr ""
# Dialog before playback
msgctxt "#33013"
@ -724,8 +877,8 @@ msgstr ""
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgstr "[黄色]现在缓存所有图像到Kodi texture缓存[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr ""
# Appended to a listed PMS if it is in the same LAN network as PKC
msgctxt "#39022"
@ -795,6 +948,16 @@ msgid ""
"paths smb://NAS/mystuff"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -868,8 +1031,8 @@ msgstr "不起作用?试试完全重置!"
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgstr "[黄色]从列表中选择Plex服务器[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr ""
# PKC Settings - Sync
msgctxt "#39051"
@ -898,19 +1061,15 @@ msgstr "正搜索Plex服务器"
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgstr "用于同步和何时尝试直接播放"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
# PKC Settings, category name
msgctxt "#39057"
msgid "Customize Paths"
msgstr "自定义路径"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "扩展Plex TV Series \"On Deck\"视图到所有节目"
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -950,10 +1109,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "最近添加:同时显示已观看电影(Refresh Plex 列表/节点!)"
msgid "Recently Added: Also show already watched movies"
msgstr ""
# PKC Settings - Connection
msgctxt "#39067"
@ -962,8 +1119,8 @@ msgstr "您当前的Plex媒体服务器"
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgstr "[黄色]手动输入Plex媒体服务器地址[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr ""
# PKC Settings - Connection
msgctxt "#39069"
@ -980,6 +1137,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "当前plex.tv状态"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -990,10 +1152,10 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "电视节目"
# PKC Settings - Playback
# PKC Settings - Sync
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgstr "如可能始终使用默认Plex字幕"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
@ -1004,8 +1166,8 @@ msgstr "如果您使用了同一类多个Plex库e.g. “儿童电影”和”
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgstr "小部件上显示的PMS项目数(e.g. \"On Deck\")"
msgid "Maximum number of videos to show in widgets"
msgstr ""
# PKC Settings - Plex
msgctxt "#39078"
@ -1019,6 +1181,65 @@ msgid ""
"settings."
msgstr "Plex Companion无法打开GDM端口。请在PKC中更改。"
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
# identical!
msgctxt "#39080"
msgid ""
"Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if "
"you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr ""
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr ""
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr ""
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr ""
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr "退出Plex家庭用户 "
@ -1027,10 +1248,6 @@ msgctxt "#39201"
msgid "Settings"
msgstr "设置"
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr "刷新Plex播放列表/节点"
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr "执行手动库同步"
@ -1056,8 +1273,8 @@ msgstr "重置PKC失败。尝试重启Kodi。"
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgstr "[黄色]切换 plex.tv 登录 (登录或退出)[/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr ""
msgctxt "#39210"
msgid "Not yet connected to Plex Server"
@ -1067,20 +1284,19 @@ msgctxt "#39211"
msgid "Watch later"
msgstr "稍后观看"
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by
# e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgstr "处于离线状态"
msgid "{0} offline"
msgstr ""
msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "输入您的Plex媒体服务器的 IP 或 URL例如"
msgctxt "#39217"
msgid ""
"Does your Plex Media Server support SSL connections? (https instead of "
"http)?"
msgstr "你Plex媒体服务器是否支持 SSL 连接?(https而不是 http)"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr ""
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1100,8 +1316,8 @@ msgid "plex.tv toggle successful"
msgstr "plex.tv 切换成功"
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgstr "[黄色]从FanartTV 寻找缺失的fanart[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr ""
msgctxt "#39223"
msgid ""
@ -1127,11 +1343,26 @@ msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr "plex.tv 登录成功"
# Message in the PKC settings to display the plex.tv username. Leave the colon
# :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgstr "Plex 用户:"
msgid "Plex admin user"
msgstr ""
# Error message if user could not log in; the actual user name will be
# appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr ""
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr ""
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr ""
msgctxt "#39250"
msgid ""
@ -1197,9 +1428,9 @@ msgstr " 重置数据库前可能无法正常运行。"
msgctxt "#39403"
msgid ""
"Cancelling the database syncing process. Current Kodi version is "
"unsupported. Please verify your logs for more info."
msgstr "正在取消数据库同步进程。不支持当前Kodi版本。请验证logs以获取更多信息。"
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
msgctxt "#39405"
msgid "Plex playlists/nodes refreshed"
@ -1209,10 +1440,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr "Plex 播放列表/节点刷新失败"
msgctxt "#39407"
msgid "Full library sync finished"
msgstr "全库同步完成"
msgctxt "#39408"
msgid ""
"Sync had to skip some items because they could not be processed. Kodi may be"
@ -1238,6 +1465,10 @@ msgctxt "#39501"
msgid "Collections"
msgstr "收藏"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600"
msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1288,10 +1519,9 @@ msgctxt "#39705"
msgid "Use at your own risk"
msgstr ""
# If user gets prompted to choose between several subtitles. Leave the number
# one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgid "Don't burn-in any subtitle"
msgstr ""
# If user gets prompted to choose between several audio/subtitle tracks and
@ -1344,7 +1574,7 @@ msgstr ""
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgid "Synching playlists"
msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is

View file

@ -1,12 +1,13 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2017
#
msgid ""
msgstr ""
"Project-Id-Version: PlexKodiConnect\n"
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2017\n"
"Language-Team: Chinese (Taiwan) (https://www.transifex.com/croneter/teams/73837/zh_TW/)\n"
"MIME-Version: 1.0\n"
@ -41,6 +42,13 @@ msgid ""
"could break PKC. Deactivate?"
msgstr ""
msgctxt "#30004"
msgid ""
"The Kodi webserver is needed for artwork caching. PKC already set a strong, "
"random password automatically if you haven't done so already. Please confirm"
" the next dialog that you want to enable the webserver now with Yes."
msgstr ""
msgctxt "#30005"
msgid "Username: "
msgstr "使用者: "
@ -72,7 +80,7 @@ msgstr ""
# PKC settings - Artwork
msgctxt "#30011"
msgid "Plex artwork (posters and backgrounds) left to cache:"
msgid "Artwork left to cache:"
msgstr ""
# Button text
@ -89,9 +97,9 @@ msgctxt "#30014"
msgid "Connection"
msgstr "連結"
# PKC settings - Artwork
# Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015"
msgid "Movie and show FanartTV lookups left to do:"
msgid "Fanart download already running"
msgstr ""
msgctxt "#30016"
@ -108,16 +116,31 @@ msgctxt "#30018"
msgid "Checking FanartTV for %s items"
msgstr ""
# Sync notification displayed when FanartTV lookup is completed
# PKC settings artwork options: status info
msgctxt "#30019"
msgid "FanartTV lookup completed"
msgstr ""
# PKC settings sync options
msgctxt "#30020"
msgid "Sync Plex playlists (reboot Kodi!)"
msgstr ""
# PKC settings sync options
msgctxt "#30021"
msgid "Only sync specific Plex playlists to Kodi"
msgstr ""
# PKC settings category
msgctxt "#30022"
msgid "Advanced"
msgstr "進階"
# PKC settings sync options
msgctxt "#30023"
msgid "Only sync specific Kodi playlists to Plex"
msgstr ""
msgctxt "#30024"
msgid "Username"
msgstr "使用者"
@ -126,6 +149,29 @@ msgctxt "#30025"
msgid "Display message if PMS goes offline"
msgstr "顯示\"伺服器未連線\""
# PKC settings sync options
msgctxt "#30026"
msgid "Prefix in Plex playlist name to trigger sync"
msgstr ""
# PKC settings sync options
msgctxt "#30027"
msgid "Prefix in Kodi playlist name to trigger sync"
msgstr ""
# PKC settings artwork options: status info
msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr ""
# Warning shown when PKC switches to the Kodi default skin Estuary
msgctxt "#30029"
msgid ""
"To ensure a smooth PlexKodiConnect experience, it is HIGHLY recommended to "
"use Kodi's default skin \"Estuary\" for initial set-up and for possible "
"database resets. Continue?"
msgstr ""
msgctxt "#30030"
msgid "Port Number"
msgstr "埠號"
@ -225,6 +271,10 @@ msgctxt "#30160"
msgid "Video Quality if Transcoding necessary"
msgstr "轉碼影像品質"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
msgctxt "#30165"
msgid "Direct Play"
msgstr "直播"
@ -281,6 +331,42 @@ msgctxt "#30198"
msgid "Search"
msgstr "搜尋"
# For setting up direct paths and adding network credentials
msgctxt "#30200"
msgid ""
"In the following window, enter the server's hostname (or IP) where your Plex"
" media resides. Mind the case!"
msgstr ""
# For setting up direct paths and adding network credentials - input window
# for hostname
msgctxt "#30201"
msgid "Enter server hostname (or IP)"
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30202"
msgid ""
"In the following window, enter the network protocol you would like to use. "
"This is likely 'smb'."
msgstr ""
# For setting up direct paths and adding network credentials - input window
# protocol
msgctxt "#30203"
msgid "Enter network protocol"
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30204"
msgid "The hostname or IP '{0}' that you entered is not valid."
msgstr ""
# For setting up direct paths and adding network credentials
msgctxt "#30205"
msgid "The protocol '{0}' that you entered is not supported."
msgstr ""
# Video node naming for random e.g. movies
msgctxt "#30227"
msgid "Random"
@ -392,6 +478,12 @@ msgctxt "#30416"
msgid "Settings for the Plex Server"
msgstr "Plex 伺服器的設置"
msgctxt "#30417"
msgid ""
"Could not change the Kodi settings file {0}. PKC might not work correctly. "
"Error: {1}"
msgstr ""
# PKC Settings - Connection
msgctxt "#30500"
msgid "Verify Host SSL Certificate (more secure)"
@ -402,10 +494,15 @@ msgctxt "#30501"
msgid "Client SSL certificate"
msgstr "用戶端 SSL 憑證"
# PKC Settings - Connection
msgctxt "#30505"
msgid "[COLOR yellow]Reset login attempts[/COLOR]"
msgstr "[COLOR yellow]重置嘗試登入次數[/COLOR]"
# PKC Settings - Artwork
msgctxt "#30502"
msgid "Sync Plex artwork from the PMS (recommended)"
msgstr ""
# Message shown if SSL HTTPS certificate fails
msgctxt "#30503"
msgid "SSL certificate failed to validate. Please check {0} for solutions."
msgstr ""
# PKC Settings, category name
msgctxt "#30506"
@ -446,6 +543,11 @@ msgctxt "#30513"
msgid "Limit artwork cache threads (recommended for rpi)"
msgstr "限制快取背景海報(對Raspberry Pi是必須的)"
# PKC Settings - Sync Options
msgctxt "#30514"
msgid "Show all Plex extras instead of immediately playing trailers"
msgstr ""
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
@ -458,8 +560,8 @@ msgstr "播放"
# PKC Settings - Connection
msgctxt "#30517"
msgid "[COLOR yellow]Enter network credentials[/COLOR]"
msgstr "[COLOR yellow]輸入網路通行訊息[/COLOR]"
msgid "Set network credentials for Direct Paths and direct play"
msgstr ""
# PKC Settings - Playback
msgctxt "#30518"
@ -486,6 +588,21 @@ msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "播放 h265/HEVC 時,強迫轉碼"
# PKC Settings - Sync Options
msgctxt "#30523"
msgid "Also show sync progress for playstate and user data"
msgstr ""
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr ""
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -548,14 +665,19 @@ msgstr "從 FanArtTV 下載電影合輯海報"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "不要要求挑選特定的 串流/品質"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
msgid "Always pick best quality for trailers"
msgstr "總是挑選最好品質的預告片"
# PKC Settings - Artwork
msgctxt "#30543"
msgid "Prefer Kodi artwork for collections/sets"
msgstr ""
msgctxt "#30544"
msgid "Artwork"
msgstr "背景海報"
@ -565,6 +687,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "強制圖片轉碼"
# PKC Settings - Playback
msgctxt "#30546"
msgid "Pick the first video if several versions are present"
msgstr ""
# PKC Settings - Playback
msgctxt "#30547"
msgid "Who picks the audio stream on playback start?"
msgstr ""
# PKC Settings - Playback
msgctxt "#30548"
msgid "Who picks subtitles on playback start?"
msgstr ""
# Welcome to Plex notification
msgctxt "#33000"
msgid "Welcome"
@ -585,14 +722,29 @@ msgctxt "#33003"
msgid "Server is online"
msgstr "伺服器已上線"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr ""
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr ""
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "無效的使用者名稱或密碼"
msgctxt "#33010"
msgid "Failed to authenticate too many times. Reset in the settings."
msgstr "未能通過身份驗證的次數太多。請重置設定。"
msgid "User is unauthorized for server {0}"
msgstr ""
msgctxt "#33011"
msgid "Plex.tv did not provide us a valid list of Plex users, sorry."
msgstr ""
# Dialog before playback
msgctxt "#33013"
@ -723,8 +875,8 @@ msgstr ""
# PKC Settings - Artwork
msgctxt "#39020"
msgid "[COLOR yellow]Cache all images to Kodi texture cache now[/COLOR]"
msgstr "[COLOR yellow]立即暫存所有圖像到Kodi的圖像暫存區[/COLOR]"
msgid "Cache all images to Kodi texture cache now"
msgstr ""
# Appended to a listed PMS if it is in the same LAN network as PKC
msgctxt "#39022"
@ -794,6 +946,16 @@ msgid ""
"paths smb://NAS/mystuff"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -865,8 +1027,8 @@ msgstr "完全掛點?試著完全重置 "
# PKC Settings - Connection
msgctxt "#39050"
msgid "[COLOR yellow]Choose Plex Server from a list[/COLOR]"
msgstr "[COLOR yellow]選取Plex伺服器[/COLOR]"
msgid "Choose Plex Server from a list"
msgstr ""
# PKC Settings - Sync
msgctxt "#39051"
@ -895,19 +1057,15 @@ msgstr "正在搜索Plex伺服器"
# PKC Settings - Customize paths
msgctxt "#39056"
msgid "Used by Sync and when attempting to Direct Play"
msgstr "通過同步和嘗試使用直接播放"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
# PKC Settings, category name
msgctxt "#39057"
msgid "Customize Paths"
msgstr "自訂路徑"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "延伸plex電視節目系列\"上架\"視圖,到所有節目"
# PKC Settings - Appearance Tweaks
msgctxt "#39059"
msgid "Recently Added: Append show title to episode"
@ -947,10 +1105,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "最近添加︰ 也顯示已觀看的電影 (刷新Plex播放清單/節點!)"
msgid "Recently Added: Also show already watched movies"
msgstr ""
# PKC Settings - Connection
msgctxt "#39067"
@ -959,8 +1115,8 @@ msgstr "您當前的Plex媒體伺服器"
# PKC Settings - Connection
msgctxt "#39068"
msgid "[COLOR yellow]Manually enter Plex Media Server address[/COLOR]"
msgstr "[COLOR yellow]手動輸入Plex媒體伺服器位址[/COLOR]"
msgid "Manually enter Plex Media Server address"
msgstr ""
# PKC Settings - Connection
msgctxt "#39069"
@ -977,6 +1133,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "plex.tv 狀態︰"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -987,10 +1148,10 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "電視節目"
# PKC Settings - Playback
# PKC Settings - Sync
msgctxt "#39075"
msgid "Always use default Plex subtitle if possible"
msgstr "如果可能的話,使用預設 Plex 字幕"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
@ -1001,8 +1162,8 @@ msgstr "如果您使用多個同類的Plex資料庫例如\"兒童電影\"和\
# PKC Settings - Appearance Tweaks
msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")"
msgstr "PMS 中顯示在小工具集 (例如\"上架\") 品項的數目"
msgid "Maximum number of videos to show in widgets"
msgstr ""
# PKC Settings - Plex
msgctxt "#39078"
@ -1016,6 +1177,65 @@ msgid ""
"settings."
msgstr "Plex Companion不能打開 GDM 埠。請在 PKC 設置中更改它。"
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
# identical!
msgctxt "#39080"
msgid ""
"Use Add-on Paths (default, easy) or Direct Paths? Choose Add-on Paths if "
"you're unsure. PKC will not work if your Direct Paths setup is wrong!"
msgstr ""
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr ""
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr ""
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr ""
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39093"
msgid "Suspended - not connected"
msgstr ""
# PKC Settings - Connection - Background sync connection status
msgctxt "#39094"
msgid "Managed Plex User - not connected"
msgstr ""
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr "登出Plex Home用戶 "
@ -1024,10 +1244,6 @@ msgctxt "#39201"
msgid "Settings"
msgstr "設置"
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr "刷新Plex的播放清單/節點"
msgctxt "#39204"
msgid "Perform manual library sync"
msgstr "手動執行資料庫同步"
@ -1053,8 +1269,8 @@ msgstr "重置 PKC 失敗。嘗試重開Kodi。"
# PKC Settings - Plex
msgctxt "#39209"
msgid "[COLOR yellow]Toggle plex.tv login (sign in or sign out)[/COLOR]"
msgstr "[COLOR yellow]切換 plex.tv 帳號 (登錄或登出) [/COLOR]"
msgid "Toggle plex.tv login (sign in or sign out)"
msgstr ""
msgctxt "#39210"
msgid "Not yet connected to Plex Server"
@ -1064,20 +1280,19 @@ msgctxt "#39211"
msgid "Watch later"
msgstr "稍後再看"
# String attached at the end to get something like "PMS Name is offline"
# Error message pop-up if {0} cannot be contacted. {0} will be replaced by
# e.g. the PMS' name
msgctxt "#39213"
msgid "is offline"
msgstr "處於離線狀態"
msgid "{0} offline"
msgstr ""
msgctxt "#39215"
msgid "Enter your Plex Media Server's IP or URL, Examples are:"
msgstr "輸入您的Plex媒體伺服器的 IP 或 URL例子"
msgctxt "#39217"
msgid ""
"Does your Plex Media Server support SSL connections? (https instead of "
"http)?"
msgstr "你的Plex媒體伺服器是否支援 SSL 連線?(HTTPS 而不是 HTTP) "
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr ""
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1097,8 +1312,8 @@ msgid "plex.tv toggle successful"
msgstr "plex.tv 切換成功"
msgctxt "#39222"
msgid "[COLOR yellow]Look for missing fanart on FanartTV now[/COLOR]"
msgstr "[COLOR yellow]由FanartTV搜尋漏失的背景海報中[/COLOR]"
msgid "Look for missing fanart on FanartTV now"
msgstr ""
msgctxt "#39223"
msgid ""
@ -1124,10 +1339,25 @@ msgctxt "#39227"
msgid "Logged in to plex.tv"
msgstr ""
# Message in the PKC settings to display the plex.tv username. Leave the colon
# :
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex user:"
msgid "Plex admin user"
msgstr ""
# Error message if user could not log in; the actual user name will be
# appended at the end of the string
msgctxt "#39229"
msgid "Login failed with plex.tv for user"
msgstr ""
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
msgid "Logged in Plex home user"
msgstr ""
# Message in the PKC settings to change the logged in Plex home user
msgctxt "#39231"
msgid "Change logged in Plex home user"
msgstr ""
msgctxt "#39250"
@ -1194,9 +1424,9 @@ msgstr " 可能無法正常運行,直到重置資料庫。"
msgctxt "#39403"
msgid ""
"Cancelling the database syncing process. Current Kodi version is "
"unsupported. Please verify your logs for more info."
msgstr "取消資料庫同步程序。不支援當前kodi版本。請檢查您的日誌了解更多的資訊。"
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
msgctxt "#39405"
msgid "Plex playlists/nodes refreshed"
@ -1206,10 +1436,6 @@ msgctxt "#39406"
msgid "Plex playlists/nodes refresh failed"
msgstr "Plex 播放清單/節點刷新失敗"
msgctxt "#39407"
msgid "Full library sync finished"
msgstr "所有資料庫同步完成"
msgctxt "#39408"
msgid ""
"Sync had to skip some items because they could not be processed. Kodi may be"
@ -1235,6 +1461,10 @@ msgctxt "#39501"
msgid "Collections"
msgstr "收藏"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600"
msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1285,10 +1515,9 @@ msgctxt "#39705"
msgid "Use at your own risk"
msgstr ""
# If user gets prompted to choose between several subtitles. Leave the number
# one at the beginning of the string!
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "1 No subtitles"
msgid "Don't burn-in any subtitle"
msgstr ""
# If user gets prompted to choose between several audio/subtitle tracks and
@ -1341,7 +1570,7 @@ msgstr ""
# Shown during sync process
msgctxt "#39715"
msgid "items"
msgid "Synching playlists"
msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is

File diff suppressed because it is too large Load diff

View file

@ -1,828 +0,0 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from urllib import urlencode, quote_plus
from ast import literal_eval
from urlparse import urlparse, parse_qsl
from re import compile as re_compile
from copy import deepcopy
from time import time
from threading import Thread
from xbmc import sleep
from downloadutils import DownloadUtils as DU
from utils import settings, try_encode, try_decode
from variables import PLEX_TO_KODI_TIMEFACTOR
import plex_tv
###############################################################################
LOG = getLogger("PLEX." + __name__)
CONTAINERSIZE = int(settings('limitindex'))
REGEX_PLEX_KEY = re_compile(r'''/(.+)/(\d+)$''')
REGEX_PLEX_DIRECT = re_compile(r'''\.plex\.direct:\d+$''')
# For discovery of PMS in the local LAN
PLEX_GDM_IP = '239.0.0.250' # multicast to PMS
PLEX_GDM_PORT = 32414
PLEX_GDM_MSG = 'M-SEARCH * HTTP/1.0'
###############################################################################
def ConvertPlexToKodiTime(plexTime):
"""
Converts Plextime to Koditime. Returns an int (in seconds).
"""
if plexTime is None:
return None
return int(float(plexTime) * PLEX_TO_KODI_TIMEFACTOR)
def GetPlexKeyNumber(plexKey):
"""
Deconstructs e.g. '/library/metadata/xxxx' to the tuple
('library/metadata', 'xxxx')
Returns ('','') if nothing is found
"""
try:
result = REGEX_PLEX_KEY.findall(plexKey)[0]
except IndexError:
result = ('', '')
return result
def ParseContainerKey(containerKey):
"""
Parses e.g. /playQueues/3045?own=1&repeat=0&window=200 to:
'playQueues', '3045', {'window': '200', 'own': '1', 'repeat': '0'}
Output hence: library, key, query (str, str, dict)
"""
result = urlparse(containerKey)
library, key = GetPlexKeyNumber(result.path)
query = dict(parse_qsl(result.query))
return library, key, query
def LiteralEval(string):
"""
Turns a string e.g. in a dict, safely :-)
"""
return literal_eval(string)
def GetMethodFromPlexType(plexType):
methods = {
'movie': 'add_update',
'episode': 'add_updateEpisode',
'show': 'add_update',
'season': 'add_updateSeason',
'track': 'add_updateSong',
'album': 'add_updateAlbum',
'artist': 'add_updateArtist'
}
return methods[plexType]
def GetPlexLoginFromSettings():
"""
Returns a dict:
'plexLogin': settings('plexLogin'),
'plexToken': settings('plexToken'),
'plexhome': settings('plexhome'),
'plexid': settings('plexid'),
'myplexlogin': settings('myplexlogin'),
'plexAvatar': settings('plexAvatar'),
'plexHomeSize': settings('plexHomeSize')
Returns strings or unicode
Returns empty strings '' for a setting if not found.
myplexlogin is 'true' if user opted to log into plex.tv (the default)
plexhome is 'true' if plex home is used (the default)
"""
return {
'plexLogin': settings('plexLogin'),
'plexToken': settings('plexToken'),
'plexhome': settings('plexhome'),
'plexid': settings('plexid'),
'myplexlogin': settings('myplexlogin'),
'plexAvatar': settings('plexAvatar'),
'plexHomeSize': settings('plexHomeSize')
}
def check_connection(url, token=None, verifySSL=None):
"""
Checks connection to a Plex server, available at url. Can also be used
to check for connection with plex.tv.
Override SSL to skip the check by setting verifySSL=False
if 'None', SSL will be checked (standard requests setting)
if 'True', SSL settings from file settings are used (False/True)
Input:
url URL to Plex server (e.g. https://192.168.1.1:32400)
token appropriate token to access server. If None is passed,
the current token is used
Output:
False if server could not be reached or timeout occured
200 if connection was successfull
int or other HTML status codes as received from the server
"""
# Add '/clients' to URL because then an authentication is necessary
# If a plex.tv URL was passed, this does not work.
header_options = None
if token is not None:
header_options = {'X-Plex-Token': token}
if verifySSL is True:
verifySSL = None if settings('sslverify') == 'true' else False
if 'plex.tv' in url:
url = 'https://plex.tv/api/home/users'
else:
url = url + '/library/onDeck'
LOG.debug("Checking connection to server %s with verifySSL=%s",
url, verifySSL)
answer = DU().downloadUrl(url,
authenticate=False,
headerOptions=header_options,
verifySSL=verifySSL,
timeout=10)
if answer is None:
LOG.debug("Could not connect to %s", url)
return False
try:
# xml received?
answer.attrib
except AttributeError:
if answer is True:
# Maybe no xml but connection was successful nevertheless
answer = 200
else:
# Success - we downloaded an xml!
answer = 200
# We could connect but maybe were not authenticated. No worries
LOG.debug("Checking connection successfull. Answer: %s", answer)
return answer
def discover_pms(token=None):
"""
Optional parameter:
token token for plex.tv
Returns a list of available PMS to connect to, one entry is the dict:
{
'machineIdentifier' [str] unique identifier of the PMS
'name' [str] name of the PMS
'token' [str] token needed to access that PMS
'ownername' [str] name of the owner of this PMS or None if
the owner itself supplied tries to connect
'product' e.g. 'Plex Media Server' or None
'version' e.g. '1.11.2.4772-3e...' or None
'device': e.g. 'PC' or 'Windows' or None
'platform': e.g. 'Windows', 'Android' or None
'local' [bool] True if plex.tv supplied
'publicAddressMatches'='1'
or if found using Plex GDM in the local LAN
'owned' [bool] True if it's the owner's PMS
'relay' [bool] True if plex.tv supplied 'relay'='1'
'presence' [bool] True if plex.tv supplied 'presence'='1'
'httpsRequired' [bool] True if plex.tv supplied
'httpsRequired'='1'
'scheme' [str] either 'http' or 'https'
'ip': [str] IP of the PMS, e.g. '192.168.1.1'
'port': [str] Port of the PMS, e.g. '32400'
'baseURL': [str] <scheme>://<ip>:<port> of the PMS
}
"""
LOG.info('Start discovery of Plex Media Servers')
# Look first for local PMS in the LAN
local_pms_list = _plex_gdm()
LOG.debug('PMS found in the local LAN using Plex GDM: %s', local_pms_list)
# Get PMS from plex.tv
if token:
LOG.info('Checking with plex.tv for more PMS to connect to')
plex_pms_list = _pms_list_from_plex_tv(token)
LOG.debug('PMS found on plex.tv: %s', plex_pms_list)
else:
LOG.info('No plex token supplied, only checked LAN for available PMS')
plex_pms_list = []
# Add PMS found only in the LAN to the Plex.tv PMS list
for pms in local_pms_list:
for plex_pms in plex_pms_list:
if pms['machineIdentifier'] == plex_pms['machineIdentifier']:
break
else:
# Only found PMS using GDM - add it to the PMS from plex.tv
https = _pms_https_enabled('%s:%s' % (pms['ip'], pms['port']))
if https is None:
# Error contacting url. Skip and ignore this PMS for now
LOG.error('Could not contact PMS %s but we should have', pms)
continue
elif https is True:
pms['scheme'] = 'https'
else:
pms['scheme'] = 'http'
pms['baseURL'] = '%s://%s:%s' % (pms['scheme'],
pms['ip'],
pms['port'])
plex_pms_list.append(pms)
LOG.debug('Found the following PMS in total: %s', plex_pms_list)
return plex_pms_list
def _plex_gdm():
"""
PlexGDM - looks for PMS in the local LAN and returns a list of the PMS found
"""
# Import here because we might not need to do gdm because we already
# connected to a PMS successfully in the past
import struct
import socket
# setup socket for discovery -> multicast message
gdm = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
gdm.settimeout(2.0)
# Set the time-to-live for messages to 2 for local network
ttl = struct.pack('b', 2)
gdm.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
return_data = []
try:
# Send data to the multicast group
gdm.sendto(PLEX_GDM_MSG, (PLEX_GDM_IP, PLEX_GDM_PORT))
# Look for responses from all recipients
while True:
try:
data, server = gdm.recvfrom(1024)
return_data.append({'from': server, 'data': data})
except socket.timeout:
break
except Exception as e:
# Probably error: (101, 'Network is unreachable')
LOG.error(e)
import traceback
LOG.error("Traceback:\n%s", traceback.format_exc())
finally:
gdm.close()
LOG.debug('Plex GDM returned the data: %s', return_data)
pms_list = []
for response in return_data:
# Check if we had a positive HTTP response
if '200 OK' not in response['data']:
continue
pms = {
'ip': response['from'][0],
'scheme': None,
'local': True, # Since we found it using GDM
'product': None,
'baseURL': None,
'name': None,
'version': None,
'token': None,
'ownername': None,
'device': None,
'platform': None,
'owned': None,
'relay': None,
'presence': True, # Since we're talking to the PMS
'httpsRequired': None,
}
for line in response['data'].split('\n'):
if 'Content-Type:' in line:
pms['product'] = try_decode(line.split(':')[1].strip())
elif 'Host:' in line:
pms['baseURL'] = line.split(':')[1].strip()
elif 'Name:' in line:
pms['name'] = try_decode(line.split(':')[1].strip())
elif 'Port:' in line:
pms['port'] = line.split(':')[1].strip()
elif 'Resource-Identifier:' in line:
pms['machineIdentifier'] = line.split(':')[1].strip()
elif 'Version:' in line:
pms['version'] = line.split(':')[1].strip()
pms_list.append(pms)
return pms_list
def _pms_list_from_plex_tv(token):
"""
get Plex media Server List from plex.tv/pms/resources
"""
xml = DU().downloadUrl('https://plex.tv/api/resources',
authenticate=False,
parameters={'includeHttps': 1},
headerOptions={'X-Plex-Token': token})
try:
xml.attrib
except AttributeError:
LOG.error('Could not get list of PMS from plex.tv')
return
from Queue import Queue
queue = Queue()
thread_queue = []
max_age_in_seconds = 2*60*60*24
for device in xml.findall('Device'):
if 'server' not in device.get('provides'):
# No PMS - skip
continue
if device.find('Connection') is None:
# no valid connection - skip
continue
# check MyPlex data age - skip if >2 days
info_age = time() - int(device.get('lastSeenAt'))
if info_age > max_age_in_seconds:
LOG.debug("Skip server %s not seen for 2 days", device.get('name'))
continue
pms = {
'machineIdentifier': device.get('clientIdentifier'),
'name': device.get('name'),
'token': device.get('accessToken'),
'ownername': device.get('sourceTitle'),
'product': device.get('product'), # e.g. 'Plex Media Server'
'version': device.get('productVersion'), # e.g. '1.11.2.4772-3e...'
'device': device.get('device'), # e.g. 'PC' or 'Windows'
'platform': device.get('platform'), # e.g. 'Windows', 'Android'
'local': device.get('publicAddressMatches') == '1',
'owned': device.get('owned') == '1',
'relay': device.get('relay') == '1',
'presence': device.get('presence') == '1',
'httpsRequired': device.get('httpsRequired') == '1',
'connections': []
}
# Try a local connection first, no matter what plex.tv tells us
for connection in device.findall('Connection'):
if connection.get('local') == '1':
pms['connections'].append(connection)
# Then try non-local
for connection in device.findall('Connection'):
if connection.get('local') != '1':
pms['connections'].append(connection)
# Spawn threads to ping each PMS simultaneously
thread = Thread(target=_poke_pms, args=(pms, queue))
thread_queue.append(thread)
max_threads = 5
threads = []
# poke PMS, own thread for each PMS
while True:
# Remove finished threads
for thread in threads:
if not thread.isAlive():
threads.remove(thread)
if len(threads) < max_threads:
try:
thread = thread_queue.pop()
except IndexError:
# We have done our work
break
else:
thread.start()
threads.append(thread)
else:
sleep(50)
# wait for requests being answered
for thread in threads:
thread.join()
# declare new PMSs
pms_list = []
while not queue.empty():
pms = queue.get()
del pms['connections']
pms_list.append(pms)
queue.task_done()
return pms_list
def _poke_pms(pms, queue):
data = pms['connections'][0].attrib
url = data['uri']
if data['local'] == '1' and REGEX_PLEX_DIRECT.findall(url):
# In case DNS resolve of plex.direct does not work, append a new
# connection that will directly access the local IP (e.g. internet down)
conn = deepcopy(pms['connections'][0])
# Overwrite plex.direct
conn.attrib['uri'] = '%s://%s:%s' % (data['protocol'],
data['address'],
data['port'])
pms['connections'].insert(1, conn)
protocol, address, port = url.split(':', 2)
address = address.replace('/', '')
xml = DU().downloadUrl('%s/identity' % url,
authenticate=False,
headerOptions={'X-Plex-Token': pms['token']},
verifySSL=False,
timeout=10)
try:
xml.attrib['machineIdentifier']
except (AttributeError, KeyError):
# No connection, delete the one we just tested
del pms['connections'][0]
if pms['connections']:
# Still got connections left, try them
return _poke_pms(pms, queue)
return
else:
# Connection successful - correct pms?
if xml.get('machineIdentifier') == pms['machineIdentifier']:
# process later
pms['baseURL'] = url
pms['scheme'] = protocol
pms['ip'] = address
pms['port'] = port
queue.put(pms)
return
LOG.info('Found a pms at %s, but the expected machineIdentifier of '
'%s did not match the one we found: %s',
url, pms['uuid'], xml.get('machineIdentifier'))
def GetPlexMetadata(key):
"""
Returns raw API metadata for key as an etree XML.
Can be called with either Plex key '/library/metadata/xxxx'metadata
OR with the digits 'xxxx' only.
Returns None or 401 if something went wrong
"""
key = str(key)
if '/library/metadata/' in key:
url = "{server}" + key
else:
url = "{server}/library/metadata/" + key
arguments = {
'checkFiles': 0,
'includeExtras': 1, # Trailers and Extras => Extras
'includeReviews': 1,
'includeRelated': 0, # Similar movies => Video -> Related
# 'includeRelatedCount': 0,
# 'includeOnDeck': 1,
# 'includeChapters': 1,
# 'includePopularLeaves': 1,
# 'includeConcerts': 1
}
url = url + '?' + urlencode(arguments)
xml = DU().downloadUrl(url)
if xml == 401:
# Either unauthorized (taken care of by doUtils) or PMS under strain
return 401
# Did we receive a valid XML?
try:
xml.attrib
# Nope we did not receive a valid XML
except AttributeError:
LOG.error("Error retrieving metadata for %s", url)
xml = None
return xml
def GetAllPlexChildren(key):
"""
Returns a list (raw xml API dump) of all Plex children for the key.
(e.g. /library/metadata/194853/children pointing to a season)
Input:
key Key to a Plex item, e.g. 12345
"""
return DownloadChunks("{server}/library/metadata/%s/children?" % key)
def GetPlexSectionResults(viewId, args=None):
"""
Returns a list (XML API dump) of all Plex items in the Plex
section with key = viewId.
Input:
args: optional dict to be urlencoded
Returns None if something went wrong
"""
url = "{server}/library/sections/%s/all?" % viewId
if args:
url += urlencode(args) + '&'
return DownloadChunks(url)
def DownloadChunks(url):
"""
Downloads PMS url in chunks of CONTAINERSIZE.
url MUST end with '?' (if no other url encoded args are present) or '&'
Returns a stitched-together xml or None.
"""
xml = None
pos = 0
error_counter = 0
while error_counter < 10:
args = {
'X-Plex-Container-Size': CONTAINERSIZE,
'X-Plex-Container-Start': pos
}
xmlpart = DU().downloadUrl(url + urlencode(args))
# If something went wrong - skip in the hope that it works next time
try:
xmlpart.attrib
except AttributeError:
LOG.error('Error while downloading chunks: %s',
url + urlencode(args))
pos += CONTAINERSIZE
error_counter += 1
continue
# Very first run: starting xml (to retain data in xml's root!)
if xml is None:
xml = deepcopy(xmlpart)
if len(xmlpart) < CONTAINERSIZE:
break
else:
pos += CONTAINERSIZE
continue
# Build answer xml - containing the entire library
for child in xmlpart:
xml.append(child)
# Done as soon as we don't receive a full complement of items
if len(xmlpart) < CONTAINERSIZE:
break
pos += CONTAINERSIZE
if error_counter == 10:
LOG.error('Fatal error while downloading chunks for %s', url)
return None
return xml
def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None):
"""
Returns a list (raw XML API dump) of all Plex subitems for the key.
(e.g. /library/sections/2/allLeaves pointing to all TV shows)
Input:
viewId Id of Plex library, e.g. '2'
lastViewedAt Unix timestamp; only retrieves PMS items viewed
since that point of time until now.
updatedAt Unix timestamp; only retrieves PMS items updated
by the PMS since that point of time until now.
If lastViewedAt and updatedAt=None, ALL PMS items are returned.
Warning: lastViewedAt and updatedAt are combined with AND by the PMS!
Relevant "master time": PMS server. I guess this COULD lead to problems,
e.g. when server and client are in different time zones.
"""
args = []
url = "{server}/library/sections/%s/allLeaves" % viewId
if lastViewedAt:
args.append('lastViewedAt>=%s' % lastViewedAt)
if updatedAt:
args.append('updatedAt>=%s' % updatedAt)
if args:
url += '?' + '&'.join(args) + '&'
else:
url += '?'
return DownloadChunks(url)
def GetPlexOnDeck(viewId):
"""
"""
return DownloadChunks("{server}/library/sections/%s/onDeck?" % viewId)
def get_plex_sections():
"""
Returns all Plex sections (libraries) of the PMS as an etree xml
"""
return DU().downloadUrl('{server}/library/sections')
def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie',
trailers=False):
"""
Returns raw API metadata XML dump for a playlist with e.g. trailers.
"""
url = "{server}/playQueues"
args = {
'type': mediatype,
'uri': ('library://' + librarySectionUUID +
'/item/%2Flibrary%2Fmetadata%2F' + itemid),
'includeChapters': '1',
'shuffle': '0',
'repeat': '0'
}
if trailers is True:
args['extrasPrefixCount'] = settings('trailerNumber')
xml = DU().downloadUrl(url + '?' + urlencode(args), action_type="POST")
try:
xml[0].tag
except (IndexError, TypeError, AttributeError):
LOG.error("Error retrieving metadata for %s", url)
return
return xml
def _pms_https_enabled(url):
"""
Returns True if the PMS can talk https, False otherwise.
None if error occured, e.g. the connection timed out
Call with e.g. url='192.168.0.1:32400' (NO http/https)
This is done by GET /identity (returns an error if https is enabled and we
are trying to use http)
Prefers HTTPS over HTTP
"""
res = DU().downloadUrl('https://%s/identity' % url,
authenticate=False,
verifySSL=False)
try:
res.attrib
except AttributeError:
# Might have SSL deactivated. Try with http
res = DU().downloadUrl('http://%s/identity' % url,
authenticate=False,
verifySSL=False)
try:
res.attrib
except AttributeError:
LOG.error("Could not contact PMS %s", url)
return None
else:
# Received a valid XML. Server wants to talk HTTP
return False
else:
# Received a valid XML. Server wants to talk HTTPS
return True
def GetMachineIdentifier(url):
"""
Returns the unique PMS machine identifier of url
Returns None if something went wrong
"""
xml = DU().downloadUrl('%s/identity' % url,
authenticate=False,
verifySSL=False,
timeout=10)
try:
machineIdentifier = xml.attrib['machineIdentifier']
except (AttributeError, KeyError):
LOG.error('Could not get the PMS machineIdentifier for %s', url)
return None
LOG.debug('Found machineIdentifier %s for the PMS %s',
machineIdentifier, url)
return machineIdentifier
def GetPMSStatus(token):
"""
token: Needs to be authorized with a master Plex token
(not a managed user token)!
Calls /status/sessions on currently active PMS. Returns a dict with:
'sessionKey':
{
'userId': Plex ID of the user (if applicable, otherwise '')
'username': Plex name (if applicable, otherwise '')
'ratingKey': Unique Plex id of item being played
}
or an empty dict.
"""
answer = {}
xml = DU().downloadUrl('{server}/status/sessions',
headerOptions={'X-Plex-Token': token})
try:
xml.attrib
except AttributeError:
return answer
for item in xml:
ratingKey = item.attrib.get('ratingKey')
sessionKey = item.attrib.get('sessionKey')
userId = item.find('User')
username = ''
if userId is not None:
username = userId.attrib.get('title', '')
userId = userId.attrib.get('id', '')
else:
userId = ''
answer[sessionKey] = {
'userId': userId,
'username': username,
'ratingKey': ratingKey
}
return answer
def scrobble(ratingKey, state):
"""
Tells the PMS to set an item's watched state to state="watched" or
state="unwatched"
"""
args = {
'key': ratingKey,
'identifier': 'com.plexapp.plugins.library'
}
if state == "watched":
url = "{server}/:/scrobble?" + urlencode(args)
elif state == "unwatched":
url = "{server}/:/unscrobble?" + urlencode(args)
else:
return
DU().downloadUrl(url)
LOG.info("Toggled watched state for Plex item %s", ratingKey)
def delete_item_from_pms(plexid):
"""
Deletes the item plexid from the Plex Media Server (and the harddrive!).
Do make sure that the currently logged in user has the credentials
Returns True if successful, False otherwise
"""
if DU().downloadUrl('{server}/library/metadata/%s' % plexid,
action_type="DELETE") is True:
LOG.info('Successfully deleted Plex id %s from the PMS', plexid)
return True
LOG.error('Could not delete Plex id %s from the PMS', plexid)
return False
def get_PMS_settings(url, token):
"""
Retrieve the PMS' settings via <url>/:/prefs
Call with url: scheme://ip:port
"""
return DU().downloadUrl(
'%s/:/prefs' % url,
authenticate=False,
verifySSL=False,
headerOptions={'X-Plex-Token': token} if token else None)
def GetUserArtworkURL(username):
"""
Returns the URL for the user's Avatar. Or False if something went
wrong.
"""
users = plex_tv.list_home_users(settings('plexToken'))
url = ''
# If an error is encountered, set to False
if not users:
LOG.info("Couldnt get user from plex.tv. No URL for user avatar")
return False
for user in users:
if username in user['title']:
url = user['thumb']
LOG.debug("Avatar url for user %s is: %s", username, url)
return url
def transcode_image_path(key, AuthToken, path, width, height):
"""
Transcode Image support
parameters:
key
AuthToken
path - source path of current XML: path[srcXML]
width
height
result:
final path to image file
"""
# external address - can we get a transcoding request for external images?
if key.startswith('http://') or key.startswith('https://'):
path = key
elif key.startswith('/'): # internal full path.
path = 'http://127.0.0.1:32400' + key
else: # internal path, add-on
path = 'http://127.0.0.1:32400' + path + '/' + key
path = try_encode(path)
# This is bogus (note the extra path component) but ATV is stupid when it
# comes to caching images, it doesn't use querystrings. Fortunately PMS is
# lenient...
transcode_path = ('/photo/:/transcode/%sx%s/%s'
% (width, height, quote_plus(path)))
args = {
'width': width,
'height': height,
'url': path
}
if AuthToken:
args['X-Plex-Token'] = AuthToken
return transcode_path + '?' + urlencode(args)

View file

@ -0,0 +1,40 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Used to save PKC's application state and share between modules. Be careful
if you invoke another PKC Python instance (!!) when e.g. PKC.movies is called
"""
from __future__ import absolute_import, division, unicode_literals
from .account import Account
from .application import App
from .connection import Connection
from .libsync import Sync
from .playstate import PlayState
ACCOUNT = None
APP = None
CONN = None
SYNC = None
PLAYSTATE = None
def init(entrypoint=False):
"""
entrypoint=True initiates only the bare minimum - for other PKC python
instances
"""
global ACCOUNT, APP, CONN, SYNC, PLAYSTATE
APP = App(entrypoint)
CONN = Connection(entrypoint)
ACCOUNT = Account(entrypoint)
SYNC = Sync(entrypoint)
if not entrypoint:
PLAYSTATE = PlayState()
def reload():
"""
Reload PKC settings from xml file, e.g. on user-switch
"""
global APP, SYNC
APP.reload()
SYNC.reload()

View file

@ -0,0 +1,147 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .. import utils
LOG = getLogger('PLEX.account')
class Account(object):
def __init__(self, entrypoint=False):
self.plex_login = None
self.plex_login_id = None
self.plex_username = None
self.plex_user_id = None
self.plex_token = None
# Personal access token per specific user and PMS
# As a rule of thumb, always use this token!
self.pms_token = None
self.avatar = None
self.myplexlogin = None
self.restricted_user = None
self.force_login = None
self._session = None
self.authenticated = False
if entrypoint:
self.load_entrypoint()
else:
utils.window('plex_authenticated', clear=True)
self.load()
def set_authenticated(self):
self.authenticated = True
utils.window('plex_authenticated', value='true')
def set_unauthenticated(self):
self.authenticated = False
utils.window('plex_authenticated', clear=True)
def reset_session(self):
try:
self._session.stopSession()
except AttributeError:
pass
from .. import downloadutils
self._session = downloadutils.DownloadUtils()
self._session.startSession(reset=True)
def load(self):
LOG.debug('Loading account settings')
# User name we used to sign in to plex.tv
self.plex_login = utils.settings('plexLogin') or None
self.plex_login_id = utils.settings('plexid') or None
# plex.tv username
self.plex_username = utils.settings('username') or None
# Plex ID of that user (e.g. for plex.tv) as a STRING
self.plex_user_id = utils.settings('userid') or None
# Token for that user for plex.tv
self.plex_token = utils.settings('plexToken') or None
# Plex token for the active PMS for the active user
# (might be diffent to plex_token)
self.pms_token = utils.settings('accessToken') or None
self.avatar = utils.settings('plexAvatar') or None
self.myplexlogin = utils.settings('myplexlogin') == 'true'
# Plex home user? Then "False"
self.restricted_user = utils.settings('plex_restricteduser') == 'true'
# Force user to enter Pin if set?
self.force_login = utils.settings('enforceUserLogin') == 'true'
# Also load these settings to Kodi window variables - they'll be
# available for other PKC Python instances
utils.window('plex_restricteduser',
value='true' if self.restricted_user else 'false')
utils.window('plex_token', value=self.plex_token or '')
utils.window('pms_token', value=self.pms_token or '')
utils.window('plexAvatar', value=self.avatar or '')
# Start download session
self.reset_session()
LOG.debug('Loaded user %s, %s with plex token %s... and pms token %s...',
self.plex_username, self.plex_user_id,
self.plex_token[:5] if self.plex_token else None,
self.pms_token[:5] if self.pms_token else None)
LOG.debug('User is restricted Home user: %s', self.restricted_user)
def load_entrypoint(self):
self.pms_token = utils.settings('accessToken') or None
def log_out(self):
LOG.debug('Logging-out user %s', self.plex_username)
self.plex_username = None
self.plex_user_id = None
self.pms_token = None
self.avatar = None
self.restricted_user = None
self.authenticated = False
try:
self._session.stopSession()
except AttributeError:
pass
self._session = None
utils.settings('username', value='')
utils.settings('userid', value='')
utils.settings('plex_restricteduser', value='')
utils.settings('accessToken', value='')
utils.settings('plexAvatar', value='')
utils.window('plex_restricteduser', clear=True)
utils.window('pms_token', clear=True)
utils.window('plexAvatar', clear=True)
utils.window('plex_authenticated', clear=True)
def clear(self):
LOG.debug('Clearing account settings')
self.plex_username = None
self.plex_user_id = None
self.plex_token = None
self.pms_token = None
self.avatar = None
self.restricted_user = None
self.authenticated = False
self.plex_login = None
self.plex_login_id = None
try:
self._session.stopSession()
except AttributeError:
pass
self._session = None
utils.settings('username', value='')
utils.settings('userid', value='')
utils.settings('plex_restricteduser', value='')
utils.settings('plexToken', value='')
utils.settings('accessToken', value='')
utils.settings('plexAvatar', value='')
utils.settings('plexLogin', value='')
utils.settings('plexid', value='')
utils.window('plex_restricteduser', clear=True)
utils.window('plex_token', clear=True)
utils.window('pms_token', clear=True)
utils.window('plexAvatar', clear=True)
utils.window('plex_authenticated', clear=True)

View file

@ -0,0 +1,173 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import Queue
from threading import Lock, RLock
import xbmc
from .. import utils
LOG = getLogger('PLEX.app')
class App(object):
"""
This class is used to store variables across PKC modules
"""
def __init__(self, entrypoint=False):
self.fetch_pms_item_number = None
self.force_reload_skin = None
# All thread instances
self.threads = []
if entrypoint:
self.load_entrypoint()
else:
self.reload()
# Quit PKC?
self.stop_pkc = False
# This will suspend the main thread also
self.suspend = False
# Update Kodi widgets
self.update_widgets = False
# Need to lock all methods and functions messing with Plex Companion subscribers
self.lock_subscriber = RLock()
# Need to lock everything messing with Kodi/PKC playqueues
self.lock_playqueues = RLock()
# Necessary to temporarily hold back librarysync/websocket listener when doing
# a full sync
self.lock_playlists = Lock()
# Plex Companion Queue()
self.companion_queue = Queue.Queue(maxsize=100)
# Websocket_client queue to communicate with librarysync
self.websocket_queue = Queue.Queue()
# xbmc.Monitor() instance from kodimonitor.py
self.monitor = None
# xbmc.Player() instance
self.player = None
# Instance of FanartThread()
self.fanart_thread = None
# Instance of ImageCachingThread()
self.caching_thread = None
# Dialog to skip intro
self.skip_intro_dialog = None
@property
def is_playing(self):
return self.player.isPlaying() == 1
@property
def is_playing_video(self):
return self.player.isPlayingVideo() == 1
def register_fanart_thread(self, thread):
self.fanart_thread = thread
self.threads.append(thread)
def deregister_fanart_thread(self, thread):
self.fanart_thread.unblock_callers()
self.fanart_thread = None
self.threads.remove(thread)
def suspend_fanart_thread(self, block=True):
try:
self.fanart_thread.suspend(block=block)
except AttributeError:
pass
def resume_fanart_thread(self):
try:
self.fanart_thread.resume()
except AttributeError:
pass
def register_caching_thread(self, thread):
self.caching_thread = thread
self.threads.append(thread)
def deregister_caching_thread(self, thread):
self.caching_thread.unblock_callers()
self.caching_thread = None
self.threads.remove(thread)
def suspend_caching_thread(self, block=True):
try:
self.caching_thread.suspend(block=block)
except AttributeError:
pass
def resume_caching_thread(self):
try:
self.caching_thread.resume()
except AttributeError:
pass
def register_thread(self, thread):
"""
Hit with thread [backgroundthread.Killablethread instance] to register
any and all threads
"""
self.threads.append(thread)
def deregister_thread(self, thread):
"""
Sync thread has done it's work and is e.g. about to die
"""
thread.unblock_callers()
self.threads.remove(thread)
def suspend_threads(self, block=True):
"""
Suspend all threads' activity with or without blocking.
Returns True only if PKC shutdown requested
"""
LOG.debug('Suspending threads: %s', self.threads)
for thread in self.threads:
thread.suspend()
if block:
while True:
for thread in self.threads:
if not thread.is_suspended():
LOG.debug('Waiting for thread to suspend: %s', thread)
# Send suspend signal again in case self.threads
# changed
thread.suspend(block=True)
else:
break
return self.monitor.abortRequested()
def resume_threads(self):
"""
Resume all thread activity with or without blocking.
Returns True only if PKC shutdown requested
"""
LOG.debug('Resuming threads: %s', self.threads)
for thread in self.threads:
thread.resume()
return self.monitor.abortRequested()
def stop_threads(self, block=True):
"""
Stop all threads. Will block until all threads are stopped
Will NOT quit if PKC should exit!
"""
LOG.debug('Killing threads: %s', self.threads)
for thread in self.threads:
thread.cancel()
if block:
while self.threads:
LOG.debug('Waiting for threads to exit: %s', self.threads)
if xbmc.sleep(100):
return True
def reload(self):
# Number of items to fetch and display in widgets
self.fetch_pms_item_number = int(utils.settings('fetch_pms_item_number'))
# Hack to force Kodi widget for "in progress" to show up if it was empty
# before
self.force_reload_skin = utils.settings('forceReloadSkinOnPlaybackStop') == 'true'
def load_entrypoint(self):
self.fetch_pms_item_number = int(utils.settings('fetch_pms_item_number'))

View file

@ -0,0 +1,98 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .. import utils, json_rpc as js, variables as v
LOG = getLogger('PLEX.connection')
class Connection(object):
def __init__(self, entrypoint=False):
self.verify_ssl_cert = None
self.ssl_cert_path = None
self.machine_identifier = None
self.server_name = None
self.https = None
self.host = None
self.port = None
self.server = None
self.online = False
self.webserver_host = None
self.webserver_port = None
self.webserver_username = None
self.webserver_password = None
if entrypoint:
self.load_entrypoint()
else:
self.load_webserver()
self.load()
# Token passed along, e.g. if playback initiated by Plex Companion. Might be
# another user playing something! Token identifies user
self.plex_transient_token = None
def load_webserver(self):
"""
PKC needs Kodi webserver to work correctly
"""
LOG.debug('Loading Kodi webserver details')
# Kodi webserver details
if js.get_setting('services.webserver') in (None, False):
# Enable the webserver, it is disabled
js.set_setting('services.webserver', True)
self.webserver_host = 'localhost'
self.webserver_port = js.get_setting('services.webserverport')
self.webserver_username = js.get_setting('services.webserverusername')
self.webserver_password = js.get_setting('services.webserverpassword')
def load(self):
LOG.debug('Loading connection settings')
# Shall we verify SSL certificates? "None" will leave SSL enabled
# Ignore this setting for Kodi >= 18 as Kodi 18 is much stricter
# with checking SSL certs
self.verify_ssl_cert = None if v.KODIVERSION >= 18 or utils.settings('sslverify') == 'true' \
else False
# Do we have an ssl certificate for PKC we need to use?
self.ssl_cert_path = utils.settings('sslcert') \
if utils.settings('sslcert') != 'None' else None
self.machine_identifier = utils.settings('plex_machineIdentifier') or None
self.server_name = utils.settings('plex_servername') or None
self.https = utils.settings('https') == 'true'
self.host = utils.settings('ipaddress') or None
self.port = int(utils.settings('port')) if utils.settings('port') else None
if not self.host:
self.server = None
elif self.https:
self.server = 'https://%s:%s' % (self.host, self.port)
else:
self.server = 'http://%s:%s' % (self.host, self.port)
self.online = False
LOG.debug('Set server %s (%s) to %s',
self.server_name, self.machine_identifier, self.server)
def load_entrypoint(self):
self.verify_ssl_cert = None if v.KODIVERSION >= 18 or utils.settings('sslverify') == 'true' \
else False
self.ssl_cert_path = utils.settings('sslcert') \
if utils.settings('sslcert') != 'None' else None
self.https = utils.settings('https') == 'true'
self.host = utils.settings('ipaddress') or None
self.port = int(utils.settings('port')) if utils.settings('port') else None
if not self.host:
self.server = None
elif self.https:
self.server = 'https://%s:%s' % (self.host, self.port)
else:
self.server = 'http://%s:%s' % (self.host, self.port)
def clear(self):
LOG.debug('Clearing connection settings')
self.machine_identifier = None
self.server_name = None
self.https = None
self.host = None
self.port = None
self.server = None

View file

@ -0,0 +1,130 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from .. import utils
def remove_trailing_slash(path):
"""
Removes trailing slashes or backslashes from path [unicode], and is NOT
dependent on os.path
"""
if '/' in path:
path = path[:-1] if path.endswith('/') else path
else:
path = path[:-1] if path.endswith('\\') else path
return path
class Sync(object):
def __init__(self, entrypoint=False):
# Direct Paths (True) or Addon Paths (False)?
self.direct_paths = None
# Is synching of Plex music enabled?
self.enable_music = None
# Do we sync artwork from the PMS to Kodi?
self.artwork = None
# Path remapping mechanism (e.g. smb paths)
# Do we replace \\myserver\path to smb://myserver/path?
self.replace_smb_path = None
# Do we generally remap?
self.remap_path = None
self.force_transcode_pix = None
# Mappings for REMAP_PATH:
self.remapSMBmovieOrg = None
self.remapSMBmovieNew = None
self.remapSMBtvOrg = None
self.remapSMBtvNew = None
self.remapSMBmusicOrg = None
self.remapSMBmusicNew = None
self.remapSMBphotoOrg = None
self.remapSMBphotoNew = None
# Escape path?
self.escape_path = None
self.escape_path_safe_chars = None
# Shall we replace custom user ratings with the number of versions available?
self.indicate_media_versions = None
# Will sync movie trailer differently: either play trailer directly or show
# all the Plex extras for the user to choose
self.show_extras_instead_of_playing_trailer = None
# Only sync specific Plex playlists to Kodi?
self.sync_specific_plex_playlists = None
# Only sync specific Kodi playlists to Plex?
self.sync_specific_kodi_playlists = None
# Shall we show Kodi dialogs when synching?
self.sync_dialog = None
# How often shall we sync?
self.full_sync_intervall = None
# How long shall we wait with synching a new item to make sure Plex got all
# metadata?
self.backgroundsync_saftymargin = None
# How many threads to download Plex metadata on sync?
self.sync_thread_number = None
# Shall Kodi show dialogs for syncing/caching images? (e.g. images left
# to sync)
self.image_sync_notifications = None
# Do we need to run a special library scan?
self.run_lib_scan = None
# Set if user decided to cancel sync
self.stop_sync = False
# Could we access the paths?
self.path_verified = False
# List of Section() items representing Plex library sections
self._sections = []
# List of section_ids we're synching to Kodi - will be automatically
# re-built if sections are set a-new
self.section_ids = set()
self.load()
@property
def sections(self):
return self._sections
@sections.setter
def sections(self, sections):
self._sections = sections
# Sets are faster when using "in" test than lists
self.section_ids = set([x.section_id for x in sections if x.sync_to_kodi])
def load(self):
self.direct_paths = utils.settings('useDirectPaths') == '1'
self.enable_music = utils.settings('enableMusic') == 'true'
self.artwork = utils.settings('usePlexArtwork') == 'true'
self.replace_smb_path = utils.settings('replaceSMB') == 'true'
self.remap_path = utils.settings('remapSMB') == 'true'
self.remapSMBmovieOrg = remove_trailing_slash(utils.settings('remapSMBmovieOrg'))
self.remapSMBmovieNew = remove_trailing_slash(utils.settings('remapSMBmovieNew'))
self.remapSMBtvOrg = remove_trailing_slash(utils.settings('remapSMBtvOrg'))
self.remapSMBtvNew = remove_trailing_slash(utils.settings('remapSMBtvNew'))
self.remapSMBmusicOrg = remove_trailing_slash(utils.settings('remapSMBmusicOrg'))
self.remapSMBmusicNew = remove_trailing_slash(utils.settings('remapSMBmusicNew'))
self.remapSMBphotoOrg = remove_trailing_slash(utils.settings('remapSMBphotoOrg'))
self.remapSMBphotoNew = remove_trailing_slash(utils.settings('remapSMBphotoNew'))
self.escape_path = utils.settings('escapePath') == 'true'
self.escape_path_safe_chars = utils.settings('escapePathSafeChars').encode('utf-8')
self.indicate_media_versions = utils.settings('indicate_media_versions') == "true"
self.sync_specific_plex_playlists = utils.settings('syncSpecificPlexPlaylists') == 'true'
self.sync_specific_kodi_playlists = utils.settings('syncSpecificKodiPlaylists') == 'true'
self.sync_thread_number = int(utils.settings('syncThreadNumber'))
self.reload()
def reload(self):
"""
Any settings unrelated to syncs to the Kodi database - can thus be
safely reset without a Kodi reboot
"""
self.sync_dialog = utils.settings('dbSyncIndicator') == 'true'
self.full_sync_intervall = int(utils.settings('fullSyncInterval')) * 60
self.backgroundsync_saftymargin = int(utils.settings('backgroundsync_saftyMargin'))
self.image_sync_notifications = utils.settings('imageSyncNotifications') == 'true'
self.force_transcode_pix = utils.settings('force_transcode_pix') == 'true'
# Trailers in Kodi DB will remain UNTIL DB is reset!
self.show_extras_instead_of_playing_trailer = utils.settings('showExtrasInsteadOfTrailer') == 'true'

View file

@ -0,0 +1,66 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
class PlayState(object):
# "empty" dict for the PLAYER_STATES above. Use copy.deepcopy to duplicate!
template = {
'type': None,
'time': {
'hours': 0,
'minutes': 0,
'seconds': 0,
'milliseconds': 0},
'totaltime': {
'hours': 0,
'minutes': 0,
'seconds': 0,
'milliseconds': 0},
'speed': 0,
'shuffled': False,
'repeat': 'off',
'position': None,
'playlistid': None,
'currentvideostream': -1,
'currentaudiostream': -1,
'subtitleenabled': False,
'currentsubtitle': -1,
'file': None,
'kodi_id': None,
'kodi_type': None,
'plex_id': None,
'plex_type': None,
'container_key': None,
'volume': 100,
'muted': False,
'playmethod': None,
'playcount': None,
'external_player': False, # bool - xbmc.Player().isExternalPlayer()
'intro_markers': [],
}
def __init__(self):
# Kodi player states - here, initial values are set
self.player_states = {
0: {},
1: {},
2: {}
}
# The LAST playstate once playback is finished
self.old_player_states = {
0: {},
1: {},
2: {}
}
self.played_info = {}
# Currently playing PKC item, a PlaylistItem()
self.item = None
# Was the playback initiated by the user using the Kodi context menu?
self.context_menu_play = False
# Set by context menu - shall we force-transcode the next playing item?
self.force_transcode = False
# Which Kodi player is/has been active? (either int 1, 2 or 3)
self.active_players = set()

View file

@ -1,346 +1,139 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from Queue import Queue, Empty
from shutil import rmtree
from urllib import quote_plus, unquote
from threading import Thread
from os import makedirs
import requests
from xbmc import sleep, translatePath
from xbmcvfs import exists
from .kodi_db import KodiVideoDB, KodiMusicDB, KodiTextureDB
from . import app, backgroundthread, utils
from utils import settings, language as lang, kodi_sql, try_encode, try_decode,\
thread_methods, dialog, exists_dir
import state
###############################################################################
LOG = getLogger("PLEX." + __name__)
LOG = getLogger('PLEX.artwork')
# Disable annoying requests warnings
requests.packages.urllib3.disable_warnings()
ARTWORK_QUEUE = Queue()
IMAGE_CACHING_SUSPENDS = ['SUSPEND_LIBRARY_THREAD', 'DB_SCAN', 'STOP_SYNC']
if not settings('imageSyncDuringPlayback') == 'true':
IMAGE_CACHING_SUSPENDS.append('SUSPEND_SYNC')
###############################################################################
# Potentially issues with limited number of threads Hence let Kodi wait till
# download is successful
TIMEOUT = (35.1, 35.1)
BATCH_SIZE = 500
def double_urlencode(text):
return quote_plus(quote_plus(text))
return utils.quote_plus(utils.quote_plus(text))
def double_urldecode(text):
return unquote(unquote(text))
return utils.unquote(utils.unquote(text))
@thread_methods(add_suspends=IMAGE_CACHING_SUSPENDS)
class Image_Cache_Thread(Thread):
sleep_between = 50
# Potentially issues with limited number of threads
# Hence let Kodi wait till download is successful
timeout = (35.1, 35.1)
class ImageCachingThread(backgroundthread.KillableThread):
def __init__(self):
self.queue = ARTWORK_QUEUE
Thread.__init__(self)
super(ImageCachingThread, self).__init__()
self.suspend_points = [(self, '_suspended')]
if not utils.settings('imageSyncDuringPlayback') == 'true':
self.suspend_points.append((app.APP, 'is_playing_video'))
def should_suspend(self):
return any(getattr(obj, attrib) for obj, attrib in self.suspend_points)
@staticmethod
def _url_generator(kind, kodi_type):
"""
Main goal is to close DB connection between calls
"""
offset = 0
i = 0
while True:
batch = []
with kind(texture_db=True) as kodidb:
texture_db = KodiTextureDB(kodiconn=kodidb.kodiconn,
artconn=kodidb.artconn,
lock=False)
for i, url in enumerate(kodidb.artwork_generator(kodi_type,
BATCH_SIZE,
offset)):
if texture_db.url_not_yet_cached(url):
batch.append(url)
if len(batch) == BATCH_SIZE:
break
offset += i
for url in batch:
yield url
if i + 1 < BATCH_SIZE:
break
def run(self):
LOG.info("---===### Starting Image_Cache_Thread ###===---")
stopped = self.stopped
suspended = self.suspended
queue = self.queue
sleep_between = self.sleep_between
counter = 0
set_zero = False
while not stopped():
# In the event the server goes offline
while suspended():
# Set in service.py
if stopped():
# Abort was requested while waiting. We should exit
LOG.info("---===### Stopped Image_Cache_Thread ###===---")
return
sleep(1000)
try:
url = queue.get(block=False)
except Empty:
if not set_zero:
# Avoid saving '0' all the time
set_zero = True
settings('caching_artwork_count', value='0')
sleep(1000)
continue
set_zero = False
if isinstance(url, ArtworkSyncMessage):
if state.IMAGE_SYNC_NOTIFICATIONS:
dialog('notification',
heading=lang(29999),
message=url.message,
icon='{plex}',
sound=False)
queue.task_done()
continue
url = double_urlencode(try_encode(url))
sleeptime = 0
while True:
try:
requests.head(
url="http://%s:%s/image/image://%s"
% (state.WEBSERVER_HOST,
state.WEBSERVER_PORT,
url),
auth=(state.WEBSERVER_USERNAME,
state.WEBSERVER_PASSWORD),
timeout=self.timeout)
except requests.Timeout:
# We don't need the result, only trigger Kodi to start the
# download. All is well
break
except requests.ConnectionError:
if stopped():
# Kodi terminated
break
# Server thinks its a DOS attack, ('error 10053')
# Wait before trying again
if sleeptime > 5:
LOG.error('Repeatedly got ConnectionError for url %s',
double_urldecode(url))
break
LOG.debug('Were trying too hard to download art, server '
'over-loaded. Sleep %s seconds before trying '
'again to download %s',
2**sleeptime, double_urldecode(url))
sleep((2**sleeptime) * 1000)
sleeptime += 1
continue
except Exception as err:
LOG.error('Unknown exception for url %s: %s'.
double_urldecode(url), err)
import traceback
LOG.error("Traceback:\n%s", traceback.format_exc())
break
# We did not even get a timeout
break
queue.task_done()
# Update the caching state in the PKC settings.
counter += 1
if counter > 20:
counter = 0
settings('caching_artwork_count', value=str(queue.qsize()))
# Sleep for a bit to reduce CPU strain
sleep(sleep_between)
LOG.info("---===### Stopped Image_Cache_Thread ###===---")
class Artwork():
enableTextureCache = settings('enableTextureCache') == "true"
if enableTextureCache:
queue = ARTWORK_QUEUE
def cache_major_artwork(self):
"""
Takes the existing Kodi library and caches posters and fanart.
Necessary because otherwise PKC caches artwork e.g. from fanart.tv
which basically blocks Kodi from getting needed artwork fast (e.g.
while browsing the library)
"""
if not self.enableTextureCache:
return
artworks = list()
# Get all posters and fanart/background for video and music
for kind in ('video', 'music'):
connection = kodi_sql(kind)
cursor = connection.cursor()
for typus in ('poster', 'fanart'):
cursor.execute('SELECT url FROM art WHERE type == ?',
(typus, ))
artworks.extend(cursor.fetchall())
connection.close()
artworks_to_cache = list()
connection = kodi_sql('texture')
cursor = connection.cursor()
for url in artworks:
query = 'SELECT url FROM texture WHERE url == ? LIMIT 1'
cursor.execute(query, (url[0], ))
if not cursor.fetchone():
artworks_to_cache.append(url)
connection.close()
if not artworks_to_cache:
LOG.info('Caching of major images to Kodi texture cache done')
# Set to "None"
settings('caching_artwork_count', value=lang(30069))
return
length = len(artworks_to_cache)
LOG.info('Caching has not been completed - caching %s major images',
length)
settings('caching_artwork_count', value=str(length))
# Caching %s Plex images
self.queue.put(ArtworkSyncMessage(lang(30006) % length))
for i, url in enumerate(artworks_to_cache):
self.queue.put(url[0])
# Plex image caching done
self.queue.put(ArtworkSyncMessage(lang(30007)))
def fullTextureCacheSync(self):
"""
This method will sync all Kodi artwork to textures13.db
and cache them locally. This takes diskspace!
"""
if not dialog('yesno', "Image Texture Cache", lang(39250)):
return
LOG.info("Doing Image Cache Sync")
# ask to rest all existing or not
if dialog('yesno', "Image Texture Cache", lang(39251)):
LOG.info("Resetting all cache data first")
# Remove all existing textures first
path = try_decode(translatePath("special://thumbnails/"))
if exists_dir(path):
rmtree(path, ignore_errors=True)
self.restore_cache_directories()
# remove all existing data from texture DB
connection = kodi_sql('texture')
cursor = connection.cursor()
query = 'SELECT tbl_name FROM sqlite_master WHERE type=?'
cursor.execute(query, ('table', ))
rows = cursor.fetchall()
for row in rows:
tableName = row[0]
if tableName != "version":
cursor.execute("DELETE FROM %s" % tableName)
connection.commit()
connection.close()
# Cache all entries in video DB
connection = kodi_sql('video')
cursor = connection.cursor()
# dont include actors
query = "SELECT url FROM art WHERE media_type != ?"
cursor.execute(query, ('actor', ))
result = cursor.fetchall()
total = len(result)
LOG.info("Image cache sync about to process %s video images", total)
connection.close()
for url in result:
self.cache_texture(url[0])
# Cache all entries in music DB
connection = kodi_sql('music')
cursor = connection.cursor()
cursor.execute("SELECT url FROM art")
result = cursor.fetchall()
total = len(result)
LOG.info("Image cache sync about to process %s music images", total)
connection.close()
for url in result:
self.cache_texture(url[0])
def cache_texture(self, url):
'''
Cache a single image url to the texture cache. url: unicode
'''
if url and self.enableTextureCache:
self.queue.put(url)
def modify_artwork(self, artworks, kodi_id, kodi_type, cursor):
"""
Pass in an artworks dict (see PlexAPI) to set an items artwork.
"""
for kodi_art, url in artworks.iteritems():
self.modify_art(url, kodi_id, kodi_type, kodi_art, cursor)
def modify_art(self, url, kodi_id, kodi_type, kodi_art, cursor):
"""
Adds or modifies the artwork of kind kodi_art (e.g. 'poster') in the
Kodi art table for item kodi_id/kodi_type. Will also cache everything
except actor portraits.
"""
query = '''
SELECT url FROM art
WHERE media_id = ? AND media_type = ? AND type = ?
LIMIT 1
'''
cursor.execute(query, (kodi_id, kodi_type, kodi_art,))
LOG.info("---===### Starting ImageCachingThread ###===---")
app.APP.register_caching_thread(self)
try:
# Update the artwork
old_url = cursor.fetchone()[0]
except TypeError:
# Add the artwork
LOG.debug('Adding Art Link for %s kodi_id %s, kodi_type %s: %s',
kodi_art, kodi_id, kodi_type, url)
query = '''
INSERT INTO art(media_id, media_type, type, url)
VALUES (?, ?, ?, ?)
'''
cursor.execute(query, (kodi_id, kodi_type, kodi_art, url))
else:
if url == old_url:
# Only cache artwork if it changed
return
self.delete_cached_artwork(old_url)
LOG.debug("Updating Art url for %s kodi_id %s, kodi_type %s to %s",
kodi_art, kodi_id, kodi_type, url)
query = '''
UPDATE art SET url = ?
WHERE media_id = ? AND media_type = ? AND type = ?
'''
cursor.execute(query, (url, kodi_id, kodi_type, kodi_art))
# Cache fanart and poster in Kodi texture cache
if kodi_type != 'actor':
self.cache_texture(url)
def delete_artwork(self, kodiId, mediaType, cursor):
query = 'SELECT url FROM art WHERE media_id = ? AND media_type = ?'
cursor.execute(query, (kodiId, mediaType,))
for row in cursor.fetchall():
self.delete_cached_artwork(row[0])
@staticmethod
def delete_cached_artwork(url):
"""
Deleted the cached artwork with path url (if it exists)
"""
connection = kodi_sql('texture')
cursor = connection.cursor()
try:
cursor.execute("SELECT cachedurl FROM texture WHERE url=? LIMIT 1",
(url,))
cachedurl = cursor.fetchone()[0]
except TypeError:
# Could not find cached url
pass
else:
# Delete thumbnail as well as the entry
path = translatePath("special://thumbnails/%s" % cachedurl)
LOG.debug("Deleting cached thumbnail: %s", path)
if exists(path):
rmtree(try_decode(path), ignore_errors=True)
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
connection.commit()
self._run()
except Exception:
utils.ERROR()
finally:
connection.close()
app.APP.deregister_caching_thread(self)
LOG.info("---===### Stopped ImageCachingThread ###===---")
@staticmethod
def restore_cache_directories():
LOG.info("Restoring cache directories...")
paths = ("", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"a", "b", "c", "d", "e", "f",
"Video", "plex")
for path in paths:
makedirs(try_decode(translatePath("special://thumbnails/%s"
% path)))
def _loop(self):
kinds = [KodiVideoDB]
if app.SYNC.enable_music:
kinds.append(KodiMusicDB)
for kind in kinds:
for kodi_type in ('poster', 'fanart'):
for url in self._url_generator(kind, kodi_type):
if self.should_suspend() or self.should_cancel():
return False
cache_url(url, self.should_suspend)
# Toggles Image caching completed to Yes
utils.settings('plex_status_image_caching', value=utils.lang(107))
return True
def _run(self):
while True:
if self._loop():
break
if self.wait_while_suspended():
break
class ArtworkSyncMessage(object):
"""
Put in artwork queue to display the message as a Kodi notification
"""
def __init__(self, message):
self.message = message
def cache_url(url, should_suspend=None):
url = double_urlencode(url)
sleeptime = 0
while True:
try:
requests.head(
url="http://%s:%s/image/image://%s"
% (app.CONN.webserver_host,
app.CONN.webserver_port,
url),
auth=(app.CONN.webserver_username,
app.CONN.webserver_password),
timeout=TIMEOUT)
except requests.Timeout:
# We don't need the result, only trigger Kodi to start the
# download. All is well
break
except requests.ConnectionError:
if app.APP.stop_pkc or (should_suspend and should_suspend()):
break
# Server thinks its a DOS attack, ('error 10053')
# Wait before trying again
# OR: Kodi refuses Webserver connection (no password set)
if sleeptime > 5:
LOG.error('Repeatedly got ConnectionError for url %s',
double_urldecode(url))
break
LOG.debug('Were trying too hard to download art, server '
'over-loaded. Sleep %s seconds before trying '
'again to download %s',
2**sleeptime, double_urldecode(url))
app.APP.monitor.waitForAbort((2**sleeptime))
sleeptime += 1
continue
except Exception as err:
LOG.error('Unknown exception for url %s: %s'.
double_urldecode(url), err)
import traceback
LOG.error("Traceback:\n%s", traceback.format_exc())
break
# We did not even get a timeout
break

View file

@ -0,0 +1,520 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from time import time as _time
import threading
import Queue
import heapq
from collections import deque
from . import utils, app, variables as v
WORKER_COUNT = 3
LOG = getLogger('PLEX.threads')
class KillableThread(threading.Thread):
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
self._canceled = False
self._suspended = False
self._is_not_suspended = threading.Event()
self._is_not_suspended.set()
self._suspension_reached = threading.Event()
self._is_not_asleep = threading.Event()
self._is_not_asleep.set()
self.suspension_timeout = None
super(KillableThread, self).__init__(group, target, name, args, kwargs)
def should_cancel(self):
"""
Returns True if the thread should be stopped immediately
"""
return self._canceled or app.APP.stop_pkc
def cancel(self):
"""
Call from another thread to stop this current thread
"""
self._canceled = True
# Make sure thread is running in order to exit quickly
self._is_not_asleep.set()
self._is_not_suspended.set()
def should_suspend(self):
"""
Returns True if the current thread should be suspended immediately
"""
return self._suspended
def suspend(self, block=False, timeout=None):
"""
Call from another thread to suspend the current thread. Provide a
timeout [float] in seconds optionally. block=True will block the caller
until the thread-to-be-suspended is indeed suspended
Will wake a thread that is asleep!
"""
self.suspension_timeout = timeout
self._suspended = True
self._is_not_suspended.clear()
# Make sure thread wakes up in order to suspend
self._is_not_asleep.set()
if block:
self._suspension_reached.wait()
def resume(self):
"""
Call from another thread to revive a suspended or asleep current thread
back to life
"""
self._suspended = False
self._is_not_asleep.set()
self._is_not_suspended.set()
def wait_while_suspended(self):
"""
Blocks until thread is not suspended anymore or the thread should
exit or for a period of self.suspension_timeout (set by the caller of
suspend())
Returns the value of should_cancel()
"""
self._suspension_reached.set()
self._is_not_suspended.wait(self.suspension_timeout)
self._suspension_reached.clear()
return self.should_cancel()
def is_suspended(self):
"""
Check from another thread whether the current thread is suspended
"""
return self._suspension_reached.is_set()
def sleep(self, timeout):
"""
Only call from the current thread in order to sleep for a period of
timeout [float, seconds]. Will unblock immediately if thread should
cancel (should_cancel()) or the thread should_suspend
"""
self._is_not_asleep.clear()
self._is_not_asleep.wait(timeout)
self._is_not_asleep.set()
def is_asleep(self):
"""
Check from another thread whether the current thread is asleep
"""
return not self._is_not_asleep.is_set()
def unblock_callers(self):
"""
Ensures that any other thread that requested this thread's suspension
is released
"""
self._suspension_reached.set()
class ProcessingQueue(Queue.Queue, object):
"""
Queue of queues that processes a queue completely before moving on to the
next queue. There's one queue per Section(). You need to initialize each
section with add_section(section) first.
Put tuples (count, item) into this queue, with count being the respective
position of the item in the queue, starting with 0 (zero).
(None, None) is the sentinel for a single queue being exhausted, added by
add_sentinel()
"""
def _init(self, maxsize):
self.queue = deque()
self._sections = deque()
self._queues = deque()
self._current_section = None
self._current_queue = None
# Item-index for the currently active queue
self._counter = 0
def _qsize(self):
return self._current_queue._qsize() if self._current_queue else 0
def _put(self, item):
for i, section in enumerate(self._sections):
if item[1]['section'] == section:
self._queues[i]._put(item)
break
else:
raise RuntimeError('Could not find section for item %s' % item[1])
def add_sentinel(self, section):
"""
Adds a new empty section as a sentinel. Call with an empty Section()
object. Call this method immediately after having added all sections
with add_section().
Once the get()-method returns None, you've received the sentinel and
you've thus exhausted the queue
"""
with self.not_full:
section.number_of_items = 1
self._add_section(section)
# Add the actual sentinel to the queue we just added
self._queues[-1]._put((None, None))
self.unfinished_tasks += 1
self.not_empty.notify()
def add_section(self, section):
"""
Add a new Section() to this Queue. Each section will be entirely
processed before moving on to the next section.
Be sure to set section.number_of_items correctly as it will signal
when processing is completely done for a specific section!
"""
with self.mutex:
self._add_section(section)
def change_section_number_of_items(self, section, number_of_items):
"""
Hit this method if you've reset section.number_of_items to make
sure we're not blocking
"""
with self.mutex:
self._change_section_number_of_items(section, number_of_items)
def _change_section_number_of_items(self, section, number_of_items):
section.number_of_items = number_of_items
if (self._current_section == section
and self._counter == number_of_items):
# We were actually waiting for more items to come in - but there
# aren't any!
self._init_next_section()
if self._qsize() > 0:
self.not_empty.notify()
def _add_section(self, section):
self._sections.append(section)
self._queues.append(
OrderedQueue() if section.plex_type == v.PLEX_TYPE_ALBUM
else Queue.Queue())
if self._current_section is None:
self._activate_next_section()
def _init_next_section(self):
"""
Call only when a section has been completely exhausted
"""
self._sections.popleft()
self._queues.popleft()
self._activate_next_section()
def _activate_next_section(self):
self._counter = 0
self._current_section = self._sections[0] if self._sections else None
self._current_queue = self._queues[0] if self._queues else None
def _get(self):
item = self._current_queue._get()
self._counter += 1
if self._counter == self._current_section.number_of_items:
self._init_next_section()
return item[1]
class OrderedQueue(Queue.PriorityQueue, object):
"""
Queue that enforces an order on the items it returns. An item you push
onto the queue must be a tuple
(index, item)
where index=-1 is the item that will be returned first. The Queue will block
until index=-1, 0, 1, 2, 3, ... is then made available
maxsize will be rather fuzzy, as _qsize returns 0 if we're still waiting
for the next smalles index. put() thus might not block always when it
should.
"""
def __init__(self, maxsize=0):
self.next_index = 0
super(OrderedQueue, self).__init__(maxsize)
def _qsize(self, len=len):
try:
return len(self.queue) if self.queue[0][0] == self.next_index else 0
except IndexError:
return 0
def _get(self, heappop=heapq.heappop):
self.next_index += 1
return heappop(self.queue)
class Tasks(list):
def add(self, task):
for t in self:
if not t.isValid():
self.remove(t)
if isinstance(task, list):
self += task
else:
self.append(task)
def cancel(self):
while self:
self.pop().cancel()
class Task(object):
def __init__(self, priority=None):
self.priority = priority
self._canceled = False
self.finished = False
def __cmp__(self, other):
return self.priority - other.priority
def start(self):
BGThreader.addTask(self)
def _run(self):
self.run()
self.finished = True
def run(self):
raise NotImplementedError
def cancel(self):
self._canceled = True
def should_cancel(self):
return self._canceled or app.APP.monitor.abortRequested()
def isValid(self):
return not self.finished and not self._canceled
class ShutdownSentinel(Task):
def run(self):
pass
class FunctionAsTask(Task):
def __init__(self, function, callback, *args, **kwargs):
self._function = function
self._callback = callback
self._args = args
self._kwargs = kwargs
super(FunctionAsTask, self).__init__()
def run(self):
result = self._function(*self._args, **self._kwargs)
if self._callback:
self._callback(result)
class MutablePriorityQueue(Queue.PriorityQueue):
def _get(self, heappop=heapq.heappop):
self.queue.sort()
return heappop(self.queue)
def lowest(self):
"""Return the lowest priority item in the queue (not reliable!)."""
self.mutex.acquire()
try:
lowest = self.queue and min(self.queue) or None
except Exception:
lowest = None
utils.ERROR(notify=True)
finally:
self.mutex.release()
return lowest
class BackgroundWorker(object):
def __init__(self, queue, name=None):
self._queue = queue
self.name = name
self._thread = None
self._abort = False
self._task = None
@staticmethod
def _runTask(task):
if task._canceled:
return
try:
task._run()
except Exception:
utils.ERROR(notify=True)
def abort(self):
self._abort = True
return self
def aborted(self):
return self._abort or app.APP.monitor.abortRequested()
def start(self):
if self._thread and self._thread.isAlive():
return
self._thread = KillableThread(target=self._queueLoop, name='BACKGROUND-WORKER({0})'.format(self.name))
self._thread.start()
def _queueLoop(self):
if self._queue.empty():
return
LOG.debug('(%s): Active', self.name)
try:
while not self.aborted():
self._task = self._queue.get_nowait()
self._runTask(self._task)
self._queue.task_done()
self._task = None
except Queue.Empty:
LOG.debug('(%s): Idle', self.name)
def shutdown(self, block=True):
self.abort()
if self._task:
self._task.cancel()
if block and self._thread and self._thread.isAlive():
LOG.debug('thread (%s): Waiting...', self.name)
self._thread.join()
LOG.debug('thread (%s): Done', self.name)
def working(self):
return self._thread and self._thread.isAlive()
class NonstoppingBackgroundWorker(BackgroundWorker):
def __init__(self, queue, name=None):
self._working = False
super(NonstoppingBackgroundWorker, self).__init__(queue, name)
def _queueLoop(self):
LOG.debug('Starting Worker %s', self.name)
while not self.aborted():
self._task = self._queue.get()
if self._task is ShutdownSentinel:
break
self._working = True
self._runTask(self._task)
self._working = False
self._queue.task_done()
self._task = None
LOG.debug('Exiting Worker %s', self.name)
def working(self):
return self._working
class BackgroundThreader:
def __init__(self, name=None, worker=BackgroundWorker, worker_count=6):
self.name = name
self._queue = MutablePriorityQueue()
self._abort = False
self.priority = -1
self.workers = [
worker(self._queue, 'queue.{0}:worker.{1}'.format(self.name, x))
for x in range(worker_count)
]
def _nextPriority(self):
self.priority += 1
return self.priority
def abort(self):
self._abort = True
for w in self.workers:
w.abort()
return self
def aborted(self):
return self._abort or app.APP.monitor.abortRequested()
def shutdown(self, block=True):
self.abort()
self.addTasksToFront([ShutdownSentinel() for _ in self.workers])
for w in self.workers:
w.shutdown(block)
def addTask(self, task):
task.priority = self._nextPriority()
self._queue.put(task)
self.startWorkers()
def addTasks(self, tasks):
for t in tasks:
t.priority = self._nextPriority()
self._queue.put(t)
self.startWorkers()
def addTasksToFront(self, tasks):
lowest = self.getLowestPrority()
if lowest is None:
return self.addTasks(tasks)
p = lowest - len(tasks)
for t in tasks:
t.priority = p
self._queue.put(t)
p += 1
self.startWorkers()
def startWorkers(self):
for w in self.workers:
w.start()
def working(self):
return not self._queue.empty() or self.hasTask()
def hasTask(self):
return any([w.working() for w in self.workers])
def getLowestPrority(self):
lowest = self._queue.lowest()
if not lowest:
return None
return lowest.priority
def moveToFront(self, qitem):
lowest = self.getLowestPrority()
if lowest is None:
return
qitem.priority = lowest - 1
class ThreaderManager:
def __init__(self,
worker=NonstoppingBackgroundWorker,
worker_count=WORKER_COUNT):
self.index = 0
self.abandoned = []
self._workerhandler = worker
self.threader = BackgroundThreader(name=str(self.index),
worker=worker,
worker_count=worker_count)
def __getattr__(self, name):
return getattr(self.threader, name)
def reset(self):
if self.threader._queue.empty() and not self.threader.hasTask():
return
self.index += 1
self.abandoned.append(self.threader.abort())
self.threader = BackgroundThreader(name=str(self.index),
worker=self._workerhandler)
def shutdown(self, block=True):
self.threader.shutdown(block)
for a in self.abandoned:
a.shutdown(block)
BGThreader = ThreaderManager()

View file

@ -1,14 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from utils import window, settings
import variables as v
import xbmc
from . import utils
from . import variables as v
###############################################################################
log = getLogger("PLEX."+__name__)
LOG = getLogger('PLEX.clientinfo')
###############################################################################
@ -31,20 +33,19 @@ def getXArgsDeviceInfo(options=None, include_token=True):
'Connection': 'keep-alive',
"Content-Type": "application/x-www-form-urlencoded",
# "Access-Control-Allow-Origin": "*",
# 'X-Plex-Language': 'en',
'X-Plex-Device': v.ADDON_NAME,
'X-Plex-Client-Platform': v.PLATFORM,
'Accept-Language': xbmc.getLanguage(xbmc.ISO_639_1),
'X-Plex-Device': v.DEVICE,
'X-Plex-Model': v.MODEL,
'X-Plex-Device-Name': v.DEVICENAME,
'X-Plex-Platform': v.PLATFORM,
# 'X-Plex-Platform-Version': 'unknown',
# 'X-Plex-Model': 'unknown',
'X-Plex-Platform-Version': v.PLATFORM_VERSION,
'X-Plex-Product': v.ADDON_NAME,
'X-Plex-Version': v.ADDON_VERSION,
'X-Plex-Client-Identifier': getDeviceId(),
'X-Plex-Provides': 'client,controller,player,pubsub-player',
}
if include_token and window('pms_token'):
xargs['X-Plex-Token'] = window('pms_token')
if include_token and utils.window('pms_token'):
xargs['X-Plex-Token'] = utils.window('pms_token')
if options is not None:
xargs.update(options)
return xargs
@ -60,26 +61,26 @@ def getDeviceId(reset=False):
"""
if reset is True:
v.PKC_MACHINE_IDENTIFIER = None
window('plex_client_Id', clear=True)
settings('plex_client_Id', value="")
utils.window('plex_client_Id', clear=True)
utils.settings('plex_client_Id', value="")
client_id = v.PKC_MACHINE_IDENTIFIER
if client_id:
return client_id
client_id = settings('plex_client_Id')
client_id = utils.settings('plex_client_Id')
# Because Kodi appears to cache file settings!!
if client_id != "" and reset is False:
v.PKC_MACHINE_IDENTIFIER = client_id
window('plex_client_Id', value=client_id)
log.info("Unique device Id plex_client_Id loaded: %s", client_id)
utils.window('plex_client_Id', value=client_id)
LOG.info("Unique device Id plex_client_Id loaded: %s", client_id)
return client_id
log.info("Generating a new deviceid.")
LOG.info("Generating a new deviceid.")
from uuid import uuid4
client_id = str(uuid4())
settings('plex_client_Id', value=client_id)
utils.settings('plex_client_Id', value=client_id)
v.PKC_MACHINE_IDENTIFIER = client_id
window('plex_client_Id', value=client_id)
log.info("Unique device Id plex_client_Id generated: %s", client_id)
utils.window('plex_client_Id', value=client_id)
LOG.info("Unique device Id plex_client_Id generated: %s", client_id)
return client_id

View file

@ -1,68 +0,0 @@
# -*- coding: utf-8 -*-
###############################################################################
import logging
from threading import Thread
from xbmc import sleep
from utils import window, thread_methods
import state
###############################################################################
LOG = logging.getLogger("PLEX." + __name__)
###############################################################################
@thread_methods
class Monitor_Window(Thread):
"""
Monitors window('plex_command') for new entries that we need to take care
of, e.g. for new plays initiated on the Kodi side with addon paths.
Adjusts state.py accordingly
"""
def run(self):
stopped = self.stopped
queue = state.COMMAND_PIPELINE_QUEUE
LOG.info("----===## Starting Kodi_Play_Client ##===----")
while not stopped():
if window('plex_command'):
value = window('plex_command')
window('plex_command', clear=True)
if value.startswith('PLAY-'):
queue.put(value.replace('PLAY-', ''))
elif value == 'SUSPEND_LIBRARY_THREAD-True':
state.SUSPEND_LIBRARY_THREAD = True
elif value == 'SUSPEND_LIBRARY_THREAD-False':
state.SUSPEND_LIBRARY_THREAD = False
elif value == 'STOP_SYNC-True':
state.STOP_SYNC = True
elif value == 'STOP_SYNC-False':
state.STOP_SYNC = False
elif value == 'PMS_STATUS-Auth':
state.PMS_STATUS = 'Auth'
elif value == 'PMS_STATUS-401':
state.PMS_STATUS = '401'
elif value == 'SUSPEND_USER_CLIENT-True':
state.SUSPEND_USER_CLIENT = True
elif value == 'SUSPEND_USER_CLIENT-False':
state.SUSPEND_USER_CLIENT = False
elif value.startswith('PLEX_TOKEN-'):
state.PLEX_TOKEN = value.replace('PLEX_TOKEN-', '') or None
elif value.startswith('PLEX_USERNAME-'):
state.PLEX_USERNAME = \
value.replace('PLEX_USERNAME-', '') or None
elif value.startswith('RUN_LIB_SCAN-'):
state.RUN_LIB_SCAN = value.replace('RUN_LIB_SCAN-', '')
elif value.startswith('CONTEXT_menu?'):
queue.put('dummy?mode=context_menu&%s'
% value.replace('CONTEXT_menu?', ''))
elif value.startswith('NAVIGATE'):
queue.put(value.replace('NAVIGATE-', ''))
else:
raise NotImplementedError('%s not implemented' % value)
else:
sleep(50)
# Put one last item into the queue to let playback_starter end
queue.put(None)
LOG.info("----===## Kodi_Play_Client stopped ##===----")

View file

@ -1,19 +1,18 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Processes Plex companion inputs from the plexbmchelper to Kodi commands
"""
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from xbmc import Player
from variables import ALEXA_TO_COMPANION
import playqueue as PQ
from PlexFunctions import GetPlexKeyNumber
import json_rpc as js
import state
from . import playqueue as PQ, plex_functions as PF
from . import json_rpc as js, variables as v, app
###############################################################################
LOG = getLogger("PLEX." + __name__)
LOG = getLogger('PLEX.companion')
###############################################################################
@ -25,7 +24,7 @@ def skip_to(params):
Does not seem to be implemented yet by Plex!
"""
playqueue_item_id = params.get('playQueueItemID')
_, plex_id = GetPlexKeyNumber(params.get('key'))
_, plex_id = PF.GetPlexKeyNumber(params.get('key'))
LOG.debug('Skipping to playQueueItemID %s, plex_id %s',
playqueue_item_id, plex_id)
found = True
@ -50,9 +49,9 @@ def convert_alexa_to_companion(dictionary):
"""
The params passed by Alexa must first be converted to Companion talk
"""
for key in dictionary:
if key in ALEXA_TO_COMPANION:
dictionary[ALEXA_TO_COMPANION[key]] = dictionary[key]
for key in list(dictionary):
if key in v.ALEXA_TO_COMPANION:
dictionary[v.ALEXA_TO_COMPANION[key]] = dictionary[key]
del dictionary[key]
@ -66,12 +65,12 @@ def process_command(request_path, params):
if request_path == 'player/playback/playMedia':
# We need to tell service.py
action = 'alexa' if params.get('deviceName') == 'Alexa' else 'playlist'
state.COMPANION_QUEUE.put({
app.APP.companion_queue.put({
'action': action,
'data': params
})
elif request_path == 'player/playback/refreshPlayQueue':
state.COMPANION_QUEUE.put({
app.APP.companion_queue.put({
'action': 'refreshPlayQueue',
'data': params
})
@ -113,7 +112,7 @@ def process_command(request_path, params):
elif request_path == "player/navigation/back":
js.input_back()
elif request_path == "player/playback/setStreams":
state.COMPANION_QUEUE.put({
app.APP.companion_queue.put({
'action': 'setStreams',
'data': params
})

View file

@ -1,17 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from os.path import join
import xbmcgui
from xbmcaddon import Addon
from utils import window
from . import utils
from . import path_ops
from . import variables as v
###############################################################################
LOG = getLogger("PLEX." + __name__)
ADDON = Addon('plugin.video.plexkodiconnect')
LOG = getLogger('PLEX.context')
ACTION_PARENT_DIR = 9
ACTION_PREVIOUS_MENU = 10
@ -44,8 +43,8 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
return self.selected_option
def onInit(self):
if window('PlexUserImage'):
self.getControl(USER_IMAGE).setImage(window('PlexUserImage'))
if utils.window('plexAvatar'):
self.getControl(USER_IMAGE).setImage(utils.window('plexAvatar'))
height = 479 + (len(self._options) * 55)
LOG.debug("options: %s", self._options)
self.list_ = self.getControl(LIST)
@ -61,15 +60,16 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
if action in (ACTION_SELECT_ITEM, ACTION_MOUSE_LEFT_CLICK):
if self.getFocusId() == LIST:
option = self.list_.getSelectedItem()
self.selected_option = option.getLabel()
self.selected_option = option.getLabel().decode('utf-8')
LOG.info('option selected: %s', self.selected_option)
self.close()
def _add_editcontrol(self, x, y, height, width, password=None):
media = join(ADDON.getAddonInfo('path'),
'resources', 'skins', 'default', 'media')
media = path_ops.path.join(
v.ADDON_PATH, 'resources', 'skins', 'default', 'media')
filename = utils.try_encode(path_ops.path.join(media, 'white.png'))
control = xbmcgui.ControlImage(0, 0, 0, 0,
filename=join(media, "white.png"),
filename=filename,
aspectRatio=0,
colorDiffuse="ff111111")
control.setPosition(x, y)

View file

@ -1,35 +1,29 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from xbmcaddon import Addon
import xbmc
import xbmcplugin
import xbmcgui
import context
import plexdb_functions as plexdb
from utils import window, settings, dialog, language as lang
import PlexFunctions as PF
from PlexAPI import API
import playqueue as PQ
import variables as v
import state
from .plex_api import API
from .plex_db import PlexDB
from . import context, plex_functions as PF, playqueue as PQ
from . import utils, variables as v, app
###############################################################################
LOG = getLogger("PLEX." + __name__)
LOG = getLogger('PLEX.context_entry')
OPTIONS = {
'Refresh': lang(30410),
'Delete': lang(30409),
'Addon': lang(30408),
# 'AddFav': lang(30405),
# 'RemoveFav': lang(30406),
# 'RateSong': lang(30407),
'Transcode': lang(30412),
'PMS_Play': lang(30415), # Use PMS to start playback
'Extras': lang(30235)
'Refresh': utils.lang(30410),
'Delete': utils.lang(30409),
'Addon': utils.lang(30408),
# 'AddFav': utils.lang(30405),
# 'RemoveFav': utils.lang(30406),
# 'RateSong': utils.lang(30407),
'Transcode': utils.lang(30412),
'PMS_Play': utils.lang(30415), # Use PMS to start playback
'Extras': utils.lang(30235)
}
###############################################################################
@ -66,22 +60,15 @@ class ContextMenu(object):
self.api = API(xml[0])
if self._select_menu():
self._action_menu()
if self._selected_option in (OPTIONS['Delete'],
OPTIONS['Refresh']):
LOG.info("refreshing container")
xbmc.sleep(500)
xbmc.executebuiltin('Container.Refresh')
@staticmethod
def _get_plex_id(kodi_id, kodi_type):
plex_id = xbmc.getInfoLabel('ListItem.Property(plexid)') or None
if not plex_id and kodi_id and kodi_type:
with plexdb.Get_Plex_DB() as plexcursor:
item = plexcursor.getItem_byKodiId(kodi_id, kodi_type)
try:
plex_id = item[0]
except TypeError:
LOG.info('Could not get the Plex id for context menu')
with PlexDB() as plexdb:
item = plexdb.item_by_kodi_id(kodi_id, kodi_type)
if item:
plex_id = item['plex_id']
return plex_id
def _select_menu(self):
@ -92,20 +79,20 @@ class ContextMenu(object):
# if user uses direct paths, give option to initiate playback via PMS
if self.api and self.api.extras():
options.append(OPTIONS['Extras'])
if state.DIRECT_PATHS and self.kodi_type in v.KODI_VIDEOTYPES:
if app.SYNC.direct_paths and self.kodi_type in v.KODI_VIDEOTYPES:
options.append(OPTIONS['PMS_Play'])
if self.kodi_type in v.KODI_VIDEOTYPES:
options.append(OPTIONS['Transcode'])
# Delete item, only if the Plex Home main user is logged in
if (window('plex_restricteduser') != 'true' and
window('plex_allows_mediaDeletion') == 'true'):
if (utils.window('plex_restricteduser') != 'true' and
utils.window('plex_allows_mediaDeletion') == 'true'):
options.append(OPTIONS['Delete'])
# Addon settings
options.append(OPTIONS['Addon'])
context_menu = context.ContextMenu(
"script-plex-context.xml",
Addon('plugin.video.plexkodiconnect').getAddonInfo('path'),
utils.try_encode(v.ADDON_PATH),
"default",
"1080i")
context_menu.set_options(options)
@ -120,7 +107,7 @@ class ContextMenu(object):
"""
selected = self._selected_option
if selected == OPTIONS['Transcode']:
state.FORCE_TRANSCODE = True
app.PLAYSTATE.force_transcode = True
self._PMS_play()
elif selected == OPTIONS['PMS_Play']:
self._PMS_play()
@ -138,14 +125,14 @@ class ContextMenu(object):
Delete item on PMS
"""
delete = True
if settings('skipContextMenu') != "true":
if not dialog("yesno", heading="{plex}", line1=lang(33041)):
if utils.settings('skipContextMenu') != "true":
if not utils.dialog("yesno", heading="{plex}", line1=utils.lang(33041)):
LOG.info("User skipped deletion for: %s", self.plex_id)
delete = False
if delete:
LOG.info("Deleting Plex item with id %s", self.plex_id)
if PF.delete_item_from_pms(self.plex_id) is False:
dialog("ok", heading="{plex}", line1=lang(30414))
utils.dialog("ok", heading="{plex}", line1=utils.lang(30414))
def _PMS_play(self):
"""
@ -154,12 +141,10 @@ class ContextMenu(object):
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_KODI_TYPE[self.kodi_type])
playqueue.clear()
state.CONTEXT_MENU_PLAY = True
handle = ('plugin://%s/?plex_id=%s&plex_type=%s&mode=play'
% (v.ADDON_TYPE[self.plex_type],
self.plex_id,
self.plex_type))
xbmc.executebuiltin('RunPlugin(%s)' % handle)
app.PLAYSTATE.context_menu_play = True
handle = self.api.fullpath(force_addon=True)[0]
handle = 'RunPlugin(%s)' % handle
xbmc.executebuiltin(handle.encode('utf-8'))
def _extras(self):
"""

97
resources/lib/db.py Normal file
View file

@ -0,0 +1,97 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sqlite3
from functools import wraps
from . import variables as v, app
from .exceptions import LockedDatabase
DB_WRITE_ATTEMPTS = 100
DB_WRITE_ATTEMPTS_TIMEOUT = 1 # in seconds
DB_CONNECTION_TIMEOUT = 10
def catch_operationalerrors(method):
"""
sqlite.OperationalError is raised immediately if another DB connection
is open, reading something that we're trying to change
So let's catch it and try again
Also see https://github.com/mattn/go-sqlite3/issues/274
"""
@wraps(method)
def wrapper(self, *args, **kwargs):
attempts = DB_WRITE_ATTEMPTS
while True:
try:
return method(self, *args, **kwargs)
except sqlite3.OperationalError as err:
if 'database is locked' not in err:
# Not an error we want to catch, so reraise it
raise
attempts -= 1
if attempts == 0:
# Reraise in order to NOT catch nested OperationalErrors
raise LockedDatabase('Database is locked')
# Need to close the transactions and begin new ones
self.kodiconn.commit()
if self.artconn:
self.artconn.commit()
if app.APP.monitor.waitForAbort(DB_WRITE_ATTEMPTS_TIMEOUT):
# PKC needs to quit
return
# Start new transactions
self.kodiconn.execute('BEGIN')
if self.artconn:
self.artconn.execute('BEGIN')
return wrapper
def _initial_db_connection_setup(conn):
"""
Set-up DB e.g. for WAL journal mode, if that hasn't already been done
before. Also start a transaction
"""
conn.execute('PRAGMA journal_mode = WAL;')
conn.execute('PRAGMA cache_size = -8000;')
conn.execute('PRAGMA synchronous = NORMAL;')
conn.execute('BEGIN')
def connect(media_type=None):
"""
Open a connection to the Kodi database.
media_type: 'video' (standard if not passed), 'plex', 'music', 'texture'
"""
if media_type == "plex":
db_path = v.DB_PLEX_PATH
elif media_type == 'plex-copy':
db_path = v.DB_PLEX_COPY_PATH
elif media_type == "music":
db_path = v.DB_MUSIC_PATH
elif media_type == "texture":
db_path = v.DB_TEXTURE_PATH
else:
db_path = v.DB_VIDEO_PATH
conn = sqlite3.connect(db_path,
timeout=DB_CONNECTION_TIMEOUT,
isolation_level=None)
attempts = DB_WRITE_ATTEMPTS
while True:
try:
_initial_db_connection_setup(conn)
except sqlite3.OperationalError as err:
if 'database is locked' not in err:
# Not an error we want to catch, so reraise it
raise
attempts -= 1
if attempts == 0:
# Reraise in order to NOT catch nested OperationalErrors
raise LockedDatabase('Database is locked')
if app.APP.monitor.waitForAbort(0.05):
# PKC needs to quit
raise LockedDatabase('Database was locked and we need to exit')
else:
break
return conn

View file

@ -0,0 +1,39 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
xml.etree.ElementTree tries to encode with text.encode('ascii') - which is
just plain BS. This etree will always return unicode, not string
"""
from __future__ import absolute_import, division, unicode_literals
# Originally tried faster cElementTree, but does NOT work reliably with Kodi
from defusedxml.ElementTree import DefusedXMLParser, _generate_etree_functions
from xml.etree.ElementTree import TreeBuilder as _TreeBuilder
from xml.etree.ElementTree import parse as _parse
from xml.etree.ElementTree import iterparse as _iterparse
from xml.etree.ElementTree import tostring
class UnicodeXMLParser(DefusedXMLParser):
"""
PKC Hack to ensure we're always receiving unicode, not str
"""
@staticmethod
def _fixtext(text):
"""
Do NOT try to convert every entry to str with entry.encode('ascii')!
"""
return text
# aliases
XMLTreeBuilder = XMLParse = UnicodeXMLParser
parse, iterparse, fromstring = _generate_etree_functions(UnicodeXMLParser,
_TreeBuilder, _parse,
_iterparse)
XML = fromstring
__all__ = ['XML', 'XMLParse', 'XMLTreeBuilder', 'fromstring', 'iterparse',
'parse', 'tostring']

View file

@ -1,14 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import xml.etree.ElementTree as etree
import requests
import requests.exceptions as exceptions
from utils import window, language as lang, dialog
import clientinfo as client
import state
from . import utils, clientinfo, app
###############################################################################
@ -16,7 +13,7 @@ import state
import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
LOG = getLogger("PLEX." + __name__)
LOG = getLogger('PLEX.download')
###############################################################################
@ -32,34 +29,23 @@ class DownloadUtils():
_shared_state = {}
# How many failed attempts before declaring PMS dead?
connectionAttempts = 2
connection_attempts = 1
count_error = 0
# How many 401 returns before declaring unauthorized?
unauthorizedAttempts = 2
unauthorized_attempts = 2
count_unauthorized = 0
# How long should we wait for an answer from the
timeout = 30.0
def __init__(self):
self.__dict__ = self._shared_state
def setServer(self, server):
def setSSL(self):
"""
Reserved for userclient only
"""
self.server = server
LOG.debug("Set server: %s", server)
def setSSL(self, verifySSL=None, certificate=None):
"""
Reserved for userclient only
verifySSL must be 'true' to enable certificate validation
certificate must be path to certificate or 'None'
"""
if verifySSL is None:
verifySSL = state.VERIFY_SSL_CERT
if certificate is None:
certificate = state.SSL_CERT_PATH
verifySSL = app.CONN.verify_ssl_cert
certificate = app.CONN.ssl_cert_path
# Set the session's parameters
self.s.verify = verifySSL
if certificate:
@ -69,53 +55,49 @@ class DownloadUtils():
def startSession(self, reset=False):
"""
User should be authenticated when this method is called (via
userclient)
User should be authenticated when this method is called
"""
# Start session
self.s = requests.Session()
self.deviceId = client.getDeviceId()
self.deviceId = clientinfo.getDeviceId()
# Attach authenticated header to the session
self.s.headers = client.getXArgsDeviceInfo()
self.s.headers = clientinfo.getXArgsDeviceInfo()
self.s.encoding = 'utf-8'
# Set SSL settings
self.setSSL()
# Set other stuff
self.setServer(window('pms_server'))
# Counters to declare PMS dead or unauthorized
# Use window variables because start of movies will be called with a
# new plugin instance - it's impossible to share data otherwise
if reset is True:
window('countUnauthorized', value='0')
window('countError', value='0')
self.count_error = 0
self.count_unauthorized = 0
# Retry connections to the server
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
LOG.info("Requests session started on: %s", self.server)
LOG.debug("Requests session started on: %s", app.CONN.server)
def stopSession(self):
try:
self.s.close()
except:
except Exception:
LOG.info("Requests session already closed")
try:
del self.s
except:
except AttributeError:
pass
LOG.info('Request session stopped')
def getHeader(self, options=None):
header = client.getXArgsDeviceInfo()
@staticmethod
def getHeader(options=None):
header = clientinfo.getXArgsDeviceInfo()
if options is not None:
header.update(options)
return header
def _doDownload(self, s, action_type, **kwargs):
@staticmethod
def _doDownload(s, action_type, **kwargs):
if action_type == "GET":
r = s.get(**kwargs)
elif action_type == "POST":
@ -131,7 +113,7 @@ class DownloadUtils():
def downloadUrl(self, url, action_type="GET", postBody=None,
parameters=None, authenticate=True, headerOptions=None,
verifySSL=True, timeout=None, return_response=False,
headerOverride=None):
headerOverride=None, reraise=False):
"""
Override SSL check with verifySSL=False
@ -157,7 +139,7 @@ class DownloadUtils():
self.startSession()
s = self.s
# Replace for the real values
url = url.replace("{server}", self.server)
url = url.replace("{server}", app.CONN.server)
else:
# User is not (yet) authenticated. Used to communicate with
# plex.tv and to check for PMS servers
@ -166,9 +148,9 @@ class DownloadUtils():
headerOptions = self.getHeader(options=headerOptions)
else:
headerOptions = headerOverride
kwargs['verify'] = state.VERIFY_SSL_CERT
if state.SSL_CERT_PATH:
kwargs['cert'] = state.SSL_CERT_PATH
kwargs['verify'] = app.CONN.verify_ssl_cert
if app.CONN.ssl_cert_path:
kwargs['cert'] = app.CONN.ssl_cert_path
# Set the variables we were passed (fallback to request session
# otherwise - faster)
@ -185,53 +167,68 @@ class DownloadUtils():
kwargs['timeout'] = timeout
# ACTUAL DOWNLOAD HAPPENING HERE
success = False
try:
r = self._doDownload(s, action_type, **kwargs)
# THE EXCEPTIONS
except requests.exceptions.SSLError as e:
except exceptions.SSLError as e:
LOG.warn("Invalid SSL certificate for: %s", url)
LOG.warn(e)
except requests.exceptions.ConnectionError as e:
if reraise:
raise
except exceptions.ConnectionError as e:
# Connection error
LOG.warn("Server unreachable at: %s", url)
LOG.warn(e)
except requests.exceptions.Timeout as e:
if reraise:
raise
except exceptions.Timeout as e:
LOG.warn("Server timeout at: %s", url)
LOG.warn(e)
except requests.exceptions.HTTPError as e:
if reraise:
raise
except exceptions.HTTPError as e:
LOG.warn('HTTP Error at %s', url)
LOG.warn(e)
except requests.exceptions.TooManyRedirects as e:
if reraise:
raise
except exceptions.TooManyRedirects as e:
LOG.warn("Too many redirects connecting to: %s", url)
LOG.warn(e)
except requests.exceptions.RequestException as e:
if reraise:
raise
except exceptions.RequestException as e:
LOG.warn("Unknown error connecting to: %s", url)
LOG.warn(e)
if reraise:
raise
except SystemExit:
LOG.info('SystemExit detected, aborting download')
self.stopSession()
except:
if reraise:
raise
except Exception:
LOG.warn('Unknown error while downloading. Traceback:')
import traceback
LOG.warn(traceback.format_exc())
if reraise:
raise
# THE RESPONSE #####
else:
success = True
# We COULD contact the PMS, hence it ain't dead
if authenticate is True:
window('countError', value='0')
self.count_error = 0
if r.status_code != 401:
window('countUnauthorized', value='0')
self.count_unauthorized = 0
if r.status_code == 204:
if return_response is True:
# return the entire response object
return r
elif r.status_code == 204:
# No body in the response
# But read (empty) content to release connection back to pool
# (see requests: keep-alive documentation)
@ -247,22 +244,16 @@ class DownloadUtils():
LOG.info(r.text)
if '401 Unauthorized' in r.text:
# Truly unauthorized
window('countUnauthorized',
value=str(int(window('countUnauthorized')) + 1))
if (int(window('countUnauthorized')) >=
self.unauthorizedAttempts):
self.count_unauthorized += 1
if self.count_unauthorized >= self.unauthorized_attempts:
LOG.warn('We seem to be truly unauthorized for PMS'
' %s ', url)
if state.PMS_STATUS not in ('401', 'Auth'):
# Tell userclient token has been revoked.
LOG.debug('Setting PMS server status to '
'unauthorized')
state.PMS_STATUS = '401'
window('plex_serverStatus', value="401")
dialog('notification',
lang(29999),
lang(30017),
icon='{error}')
# Unauthorized access, user no longer has access
app.ACCOUNT.log_out()
utils.dialog('notification',
utils.lang(29999),
utils.lang(30017),
icon='{error}')
else:
# there might be other 401 where e.g. PMS under strain
LOG.info('PMS might only be under strain')
@ -271,14 +262,11 @@ class DownloadUtils():
elif r.status_code in (200, 201):
# 200: OK
# 201: Created
if return_response is True:
# return the entire response object
return r
try:
# xml response
r = etree.fromstring(r.content)
r = utils.defused_etree.fromstring(r.content)
return r
except:
except Exception:
r.encoding = 'utf-8'
if r.text == '':
# Answer does not contain a body
@ -287,7 +275,7 @@ class DownloadUtils():
# UNICODE - JSON object
r = r.json()
return r
except:
except Exception:
if '200 OK' in r.text:
# Received fucked up OK from PMS on playstate
# update
@ -301,24 +289,19 @@ class DownloadUtils():
elif r.status_code == 403:
# E.g. deleting a PMS item
LOG.warn('PMS sent 403: Forbidden error for url %s', url)
return None
return
else:
r.encoding = 'utf-8'
LOG.warn('Unknown answer from PMS %s with status code %s. ',
url, r.status_code)
LOG.warn('Unknown answer from PMS %s with status code %s: %s',
url, r.status_code, r.text)
return True
# And now deal with the consequences of the exceptions
if authenticate is True:
# Make the addon aware of status
try:
window('countError',
value=str(int(window('countError')) + 1))
if int(window('countError')) >= self.connectionAttempts:
finally:
if not success and authenticate:
# Deal with the consequences of the exceptions
# Make the addon aware of status
self.count_error += 1
if self.count_error >= self.connection_attempts:
LOG.warn('Failed to connect to %s too many times. '
'Declare PMS dead', url)
window('plex_online', value="false")
except:
# 'countError' not yet set
pass
return None
app.CONN.online = False

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
class PlaylistError(Exception):
"""
Exception for our playlist constructs
"""
pass
class LockedDatabase(Exception):
"""
Dedicated class to make sure we're not silently catching locked DBs.
"""
pass
class SubtitleError(Exception):
"""
Exceptions relating to subtitles
"""
pass
class ProcessingNotDone(Exception):
"""
Exception to detect whether we've completed our sync and did not have to
abort or suspend.
"""
pass

View file

@ -1,137 +1,29 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from Queue import Queue
import xml.etree.ElementTree as etree
from xbmc import executebuiltin, translatePath
from xbmc import executebuiltin
from utils import settings, window, language as lang, try_decode, dialog, \
XmlKodiSetting, reboot_kodi
from migration import check_migration
from downloadutils import DownloadUtils as DU
from userclient import UserClient
from clientinfo import getDeviceId
import PlexFunctions as PF
import plex_tv
import json_rpc as js
import playqueue as PQ
from videonodes import VideoNodes
import state
import variables as v
from . import utils
from .utils import etree
from . import path_ops
from . import migration
from .downloadutils import DownloadUtils as DU, exceptions
from . import plex_functions as PF
from . import plex_tv
from . import json_rpc as js
from . import app
from . import variables as v
###############################################################################
LOG = getLogger("PLEX." + __name__)
LOG = getLogger('PLEX.initialsetup')
###############################################################################
WINDOW_PROPERTIES = (
"plex_online", "plex_serverStatus", "plex_shouldStop", "plex_dbScan",
"plex_customplayqueue", "plex_playbackProps",
"pms_token", "plex_token", "pms_server", "plex_machineIdentifier",
"plex_servername", "plex_authenticated", "PlexUserImage", "useDirectPaths",
"countError", "countUnauthorized", "plex_restricteduser",
"plex_allows_mediaDeletion", "plex_command", "plex_result",
"plex_force_transcode_pix"
)
def reload_pkc():
"""
Will reload state.py entirely and then initiate some values from the Kodi
settings file
"""
LOG.info('Start (re-)loading PKC settings')
# Reset state.py
reload(state)
# Reset window props
for prop in WINDOW_PROPERTIES:
window(prop, clear=True)
# Clear video nodes properties
VideoNodes().clearProperties()
# Initializing
state.VERIFY_SSL_CERT = settings('sslverify') == 'true'
state.SSL_CERT_PATH = settings('sslcert') \
if settings('sslcert') != 'None' else None
state.FULL_SYNC_INTERVALL = int(settings('fullSyncInterval')) * 60
state.SYNC_THREAD_NUMBER = int(settings('syncThreadNumber'))
state.SYNC_DIALOG = settings('dbSyncIndicator') == 'true'
state.ENABLE_MUSIC = settings('enableMusic') == 'true'
state.BACKGROUND_SYNC_DISABLED = settings(
'enableBackgroundSync') == 'false'
state.BACKGROUNDSYNC_SAFTYMARGIN = int(
settings('backgroundsync_saftyMargin'))
state.REPLACE_SMB_PATH = settings('replaceSMB') == 'true'
state.REMAP_PATH = settings('remapSMB') == 'true'
state.KODI_PLEX_TIME_OFFSET = float(settings('kodiplextimeoffset'))
state.FETCH_PMS_ITEM_NUMBER = settings('fetch_pms_item_number')
state.FORCE_RELOAD_SKIN = settings('forceReloadSkinOnPlaybackStop') == 'true'
# Init some Queues()
state.COMMAND_PIPELINE_QUEUE = Queue()
state.COMPANION_QUEUE = Queue(maxsize=100)
state.WEBSOCKET_QUEUE = Queue()
set_replace_paths()
set_webserver()
# To detect Kodi profile switches
window('plex_kodiProfile',
value=try_decode(translatePath("special://profile")))
getDeviceId()
# Initialize the PKC playqueues
PQ.init_playqueues()
LOG.info('Done (re-)loading PKC settings')
def set_replace_paths():
"""
Sets our values for direct paths correctly (including using lower-case
protocols like smb:// and NOT SMB://)
"""
for typus in v.REMAP_TYPE_FROM_PLEXTYPE.values():
for arg in ('Org', 'New'):
key = 'remapSMB%s%s' % (typus, arg)
value = settings(key)
if '://' in value:
protocol = value.split('://', 1)[0]
value = value.replace(protocol, protocol.lower())
setattr(state, key, value)
def set_webserver():
"""
Set the Kodi webserver details - used to set the texture cache
"""
if js.get_setting('services.webserver') in (None, False):
# Enable the webserver, it is disabled
js.set_setting('services.webserver', True)
# Set standard port and username
# set_setting('services.webserverport', 8080)
# set_setting('services.webserverusername', 'kodi')
# Webserver already enabled
state.WEBSERVER_PORT = js.get_setting('services.webserverport')
state.WEBSERVER_USERNAME = js.get_setting('services.webserverusername')
state.WEBSERVER_PASSWORD = js.get_setting('services.webserverpassword')
def _write_pms_settings(url, token):
"""
Sets certain settings for server by asking for the PMS' settings
Call with url: scheme://ip:port
"""
xml = PF.get_PMS_settings(url, token)
try:
xml.attrib
except AttributeError:
LOG.error('Could not get PMS settings for %s', url)
return
for entry in xml:
if entry.attrib.get('id', '') == 'allowMediaDeletion':
settings('plex_allows_mediaDeletion',
value=entry.attrib.get('value', 'true'))
window('plex_allows_mediaDeletion',
value=entry.attrib.get('value', 'true'))
if not path_ops.exists(v.EXTERNAL_SUBTITLE_TEMP_PATH):
path_ops.makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH)
class InitialSetup(object):
@ -141,30 +33,130 @@ class InitialSetup(object):
"""
def __init__(self):
LOG.debug('Entering initialsetup class')
self.server = UserClient().get_server()
self.serverid = settings('plex_machineIdentifier')
# Get Plex credentials from settings file, if they exist
plexdict = PF.GetPlexLoginFromSettings()
self.myplexlogin = plexdict['myplexlogin'] == 'true'
self.plex_login = plexdict['plexLogin']
self.plex_login_id = plexdict['plexid']
self.plex_token = plexdict['plexToken']
self.plexid = plexdict['plexid']
# Token for the PMS, not plex.tv
self.pms_token = settings('accessToken')
self.pms_token = utils.settings('accessToken')
if self.plex_token:
LOG.debug('Found a plex.tv token in the settings')
def write_credentials_to_settings(self):
"""
Writes Plex username, token to plex.tv and Plex id to PKC settings
"""
utils.settings('username', value=self.plex_login or '')
utils.settings('userid', value=self.plex_login_id or '')
utils.settings('plexToken', value=self.plex_token or '')
@staticmethod
def save_pms_settings(url, token):
"""
Sets certain settings for server by asking for the PMS' settings
Call with url: scheme://ip:port
"""
xml = PF.get_PMS_settings(url, token)
try:
xml.attrib
except AttributeError:
LOG.error('Could not get PMS settings for %s', url)
return
for entry in xml:
if entry.attrib.get('id', '') == 'allowMediaDeletion':
value = 'true' if entry.get('value', '1') == '1' else 'false'
utils.settings('plex_allows_mediaDeletion', value=value)
utils.window('plex_allows_mediaDeletion', value=value)
@staticmethod
def enter_new_pms_address():
LOG.info('Start getting manual PMS address and port')
# "Enter your Plex Media Server's IP or URL. Examples are:"
utils.messageDialog(utils.lang(29999),
'%s\n%s\n%s' % (utils.lang(39215),
'192.168.1.2',
'plex.myServer.org'))
# "Enter PMS IP or URL"
address = utils.dialog('input', utils.lang(39083))
if not address:
return False
port = utils.dialog('input', utils.lang(39084), '32400', type='{numeric}')
if not port:
return False
url = '%s:%s' % (address, port)
# "Use HTTPS (SSL) connections? Answer should probably be yes."
https = utils.yesno_dialog(utils.lang(29999), utils.lang(39217))
if https:
url = 'https://%s' % url
else:
url = 'http://%s' % url
https = 'true' if https else 'false'
# Try to connect first
error = False
try:
machine_identifier = PF.GetMachineIdentifier(url)
except exceptions.SSLError:
LOG.error('SSL cert error contacting %s', url)
# "SSL certificate failed to validate. Please check {0}
# for solutions."
utils.messageDialog(utils.lang(29999),
utils.lang(30503).format('github.com/croneter/PlexKodiConnect/issues'))
return
except Exception:
error = True
if error or machine_identifier is None:
LOG.error('Could not even get a machineIdentifier for %s', url)
# "Server is unreachable"
utils.messageDialog(utils.lang(29999), utils.lang(33002))
return
# Let's use the main account's token, not managed user token
token = utils.settings('plexToken')
xml = PF.pms_root(url, token)
if xml == 401:
LOG.error('Not yet authorized for %s', url)
# "User is unauthorized for server {0}",
# "Please sign in to plex.tv."
utils.messageDialog(utils.lang(29999),
'%s. %s' % (utils.lang(33010).format(address),
utils.lang(39014)))
return
try:
xml[0].attrib
except (IndexError, TypeError, AttributeError):
LOG.error('Could not get PMS root directory for %s', url)
# "Error contacting PMS"
utils.messageDialog(utils.lang(29999), utils.lang(39218))
return
pms = {
'baseURL': url,
'ip': address,
# Assume PMS is not local so we're not resetting verifyssl
'local': False,
'machineIdentifier': xml.get('machineIdentifier'),
'name': xml.get('friendlyName'),
# Assume that we own this PMS - no easy way to check
'owned': True,
'platform': xml.get('platform'),
'port': port,
# 'relay': True,
'scheme': 'https' if https else 'http',
'token': token,
'version': xml.get('version')
}
return pms
def plex_tv_sign_in(self):
"""
Signs (freshly) in to plex.tv (will be saved to file settings)
Returns True if successful, or False if not
"""
result = plex_tv.sign_in_with_pin()
if result:
self.plex_login = result['username']
self.plex_token = result['token']
self.plexid = result['plexid']
user = plex_tv.sign_in_with_pin()
if user:
self.plex_login = user.username
self.plex_token = user.authToken
self.plex_login_id = user.id
return True
return False
@ -180,20 +172,20 @@ class InitialSetup(object):
# HTTP Error: unauthorized. Token is no longer valid
LOG.info('plex.tv connection returned HTTP %s', str(chk))
# Delete token in the settings
settings('plexToken', value='')
settings('plexLogin', value='')
utils.settings('plexToken', value='')
utils.settings('plexLogin', value='')
# Could not login, please try again
dialog('ok', lang(29999), lang(39009))
utils.messageDialog(utils.lang(29999), utils.lang(39009))
answer = self.plex_tv_sign_in()
elif chk is False or chk >= 400:
# Problems connecting to plex.tv. Network or internet issue?
LOG.info('Problems connecting to plex.tv; connection returned '
'HTTP %s', str(chk))
dialog('ok', lang(29999), lang(39010))
utils.messageDialog(utils.lang(29999), utils.lang(39010))
answer = False
else:
LOG.info('plex.tv connection with token successful')
settings('plex_status', value=lang(39227))
utils.settings('plex_status', value=utils.lang(39227))
# Refresh the info from Plex.tv
xml = DU().downloadUrl('https://plex.tv/users/account',
authenticate=False,
@ -203,15 +195,13 @@ class InitialSetup(object):
except (AttributeError, KeyError):
LOG.error('Failed to update Plex info from plex.tv')
else:
settings('plexLogin', value=self.plex_login)
home = 'true' if xml.attrib.get('home') == '1' else 'false'
settings('plexhome', value=home)
settings('plexAvatar', value=xml.attrib.get('thumb'))
settings('plexHomeSize', value=xml.attrib.get('homeSize', '1'))
utils.settings('plexLogin', value=self.plex_login)
utils.settings('plexAvatar', value=xml.attrib.get('thumb'))
LOG.info('Updated Plex info from plex.tv')
return answer
def check_existing_pms(self):
@staticmethod
def check_existing_pms():
"""
Check the PMS that was set in file settings.
Will return False if we need to reconnect, because:
@ -222,26 +212,27 @@ class InitialSetup(object):
not set before
"""
answer = True
chk = PF.check_connection(self.server, verifySSL=False)
chk = PF.check_connection(app.CONN.server,
verifySSL=True if v.KODIVERSION >= 18 else False)
if chk is False:
LOG.warn('Could not reach PMS %s', self.server)
LOG.warn('Could not reach PMS %s', app.CONN.server)
answer = False
if answer is True and not self.serverid:
if answer is True and not app.CONN.machine_identifier:
LOG.info('No PMS machineIdentifier found for %s. Trying to '
'get the PMS unique ID', self.server)
self.serverid = PF.GetMachineIdentifier(self.server)
if self.serverid is None:
'get the PMS unique ID', app.CONN.server)
app.CONN.machine_identifier = PF.GetMachineIdentifier(app.CONN.server)
if app.CONN.machine_identifier is None:
LOG.warn('Could not retrieve machineIdentifier')
answer = False
else:
settings('plex_machineIdentifier', value=self.serverid)
utils.settings('plex_machineIdentifier', value=app.CONN.machine_identifier)
elif answer is True:
temp_server_id = PF.GetMachineIdentifier(self.server)
if temp_server_id != self.serverid:
temp_server_id = PF.GetMachineIdentifier(app.CONN.server)
if temp_server_id != app.CONN.machine_identifier:
LOG.warn('The current PMS %s was expected to have a '
'unique machineIdentifier of %s. But we got '
'%s. Pick a new server to be sure',
self.server, self.serverid, temp_server_id)
app.CONN.server, app.CONN.machine_identifier, temp_server_id)
answer = False
return answer
@ -250,22 +241,20 @@ class InitialSetup(object):
"""
Checks for server's connectivity. Returns check_connection result
"""
# Re-direct via plex if remote - will lead to the correct SSL
# certificate
if server['local']:
url = ('%s://%s:%s'
% (server['scheme'], server['ip'], server['port']))
# Deactive SSL verification if the server is local!
verifySSL = False
# Deactive SSL verification if the server is local for Kodi 17
verifySSL = True if v.KODIVERSION >= 18 else False
else:
url = server['baseURL']
verifySSL = True
chk = PF.check_connection(url,
token=server['token'],
verifySSL=verifySSL)
return chk
if not server['token']:
# Plex GDM: we only get the token from plex.tv after
# Sign-in to plex.tv
server['token'] = utils.settings('plexToken') or None
return PF.check_connection(server['baseURL'],
token=server['token'],
verifySSL=verifySSL)
def pick_pms(self, showDialog=False):
def pick_pms(self, showDialog=False, inform_of_search=False):
"""
Searches for PMS in local Lan and optionally (if self.plex_token set)
also on plex.tv
@ -298,19 +287,16 @@ class InitialSetup(object):
}
or None if unsuccessful
"""
server = None
# If no server is set, let user choose one
if not self.server or not self.serverid:
if not app.CONN.server or not app.CONN.machine_identifier:
showDialog = True
if showDialog is True:
server = self._user_pick_pms()
else:
server = self._auto_pick_pms()
if server is not None:
_write_pms_settings(server['baseURL'], server['token'])
server = self._auto_pick_pms(show_dialog=inform_of_search)
return server
def _auto_pick_pms(self):
def _auto_pick_pms(self, show_dialog=False):
"""
Will try to pick PMS based on machineIdentifier saved in file settings
but only once
@ -319,35 +305,47 @@ class InitialSetup(object):
"""
https_updated = False
server = None
while True:
if https_updated is False:
serverlist = PF.discover_pms(self.plex_token)
for item in serverlist:
if item.get('machineIdentifier') == self.serverid:
server = item
if server is None:
name = settings('plex_servername')
LOG.warn('The PMS you have used before with a unique '
'machineIdentifier of %s and name %s is '
'offline', self.serverid, name)
if show_dialog:
# Searching for PMS
utils.dialog('notification',
heading='{plex}',
message=utils.lang(30001),
icon='{plex}',
time=60000)
try:
while True:
if https_updated is False:
serverlist = PF.discover_pms(self.plex_token)
for item in serverlist:
if item.get('machineIdentifier') == app.CONN.machine_identifier:
server = item
if server is None:
name = utils.settings('plex_servername')
LOG.warn('The PMS you have used before with a unique '
'machineIdentifier of %s and name %s is '
'offline', app.CONN.machine_identifier, name)
return
chk = self._check_pms_connectivity(server)
if chk == 504 and https_updated is False:
# switch HTTPS to HTTP or vice-versa
if server['scheme'] == 'https':
server['scheme'] = 'http'
else:
server['scheme'] = 'https'
https_updated = True
continue
# Problems connecting
elif chk >= 400 or chk is False:
LOG.warn('Problems connecting to server %s. chk is %s',
server['name'], chk)
return
chk = self._check_pms_connectivity(server)
if chk == 504 and https_updated is False:
# switch HTTPS to HTTP or vice-versa
if server['scheme'] == 'https':
server['scheme'] = 'http'
else:
server['scheme'] = 'https'
https_updated = True
continue
# Problems connecting
elif chk >= 400 or chk is False:
LOG.warn('Problems connecting to server %s. chk is %s',
server['name'], chk)
return
LOG.info('We found a server to automatically connect to: %s',
server['name'])
return server
LOG.info('We found a server to automatically connect to: %s',
server['name'])
return server
finally:
if show_dialog:
executebuiltin("Dialog.Close(all, true)")
def _user_pick_pms(self):
"""
@ -357,18 +355,18 @@ class InitialSetup(object):
"""
https_updated = False
# Searching for PMS
dialog('notification',
heading='{plex}',
message=lang(30001),
icon='{plex}',
time=5000)
utils.dialog('notification',
heading='{plex}',
message=utils.lang(30001),
icon='{plex}',
time=60000)
while True:
if https_updated is False:
serverlist = PF.discover_pms(self.plex_token)
# Exit if no servers found
if not serverlist:
LOG.warn('No plex media servers found!')
dialog('ok', lang(29999), lang(39011))
utils.messageDialog(utils.lang(29999), utils.lang(39011))
return
# Get a nicer list
dialoglist = []
@ -376,10 +374,10 @@ class InitialSetup(object):
if server['local']:
# server is in the same network as client.
# Add"local"
msg = lang(39022)
msg = utils.lang(39022)
else:
# Add 'remote'
msg = lang(39054)
msg = utils.lang(39054)
if server.get('ownername'):
# Display username if its not our PMS
dialoglist.append('%s (%s, %s)'
@ -390,7 +388,9 @@ class InitialSetup(object):
dialoglist.append('%s (%s)'
% (server['name'], msg))
# Let user pick server from a list
resp = dialog('select', lang(39012), dialoglist)
# Close the PKC info "Searching for PMS"
executebuiltin("Dialog.Close(all, true)")
resp = utils.dialog('select', utils.lang(39012), dialoglist)
if resp == -1:
# User cancelled
return
@ -406,20 +406,20 @@ class InitialSetup(object):
if chk == 401:
LOG.warn('Not yet authorized for Plex server %s',
server['name'])
# Please sign in to plex.tv
dialog('ok',
lang(29999),
lang(39013) + server['name'],
lang(39014))
# Not yet authorized for Plex server %s
utils.messageDialog(
utils.lang(29999),
'%s %s\n%s' % (utils.lang(39013),
server['name'].decode('utf-8'),
utils.lang(39014)))
if self.plex_tv_sign_in() is False:
# Exit while loop if user cancels
return
# Problems connecting
elif chk >= 400 or chk is False:
# Problems connecting to server. Pick another server?
answ = dialog('yesno', lang(29999), lang(39015))
# Exit while loop if user chooses No
if not answ:
if not utils.yesno_dialog(utils.lang(29999), utils.lang(39015)):
# Exit while loop if user chooses No
return
# Otherwise: connection worked!
else:
@ -430,36 +430,62 @@ class InitialSetup(object):
"""
Saves server to file settings
"""
settings('plex_machineIdentifier', server['machineIdentifier'])
settings('plex_servername', server['name'])
settings('plex_serverowned', 'true' if server['owned'] else 'false')
utils.settings('plex_machineIdentifier', server['machineIdentifier'])
utils.settings('plex_servername', server['name'])
utils.settings('plex_serverowned',
'true' if server['owned'] else 'false')
# Careful to distinguish local from remote PMS
if server['local']:
scheme = server['scheme']
settings('ipaddress', server['ip'])
settings('port', server['port'])
utils.settings('ipaddress', server['ip'])
utils.settings('port', server['port'])
LOG.debug("Setting SSL verify to false, because server is "
"local")
settings('sslverify', 'false')
utils.settings('sslverify', 'false')
else:
baseURL = server['baseURL'].split(':')
scheme = baseURL[0]
settings('ipaddress', baseURL[1].replace('//', ''))
settings('port', baseURL[2])
utils.settings('ipaddress', baseURL[1].replace('//', ''))
utils.settings('port', baseURL[2])
LOG.debug("Setting SSL verify to true, because server is not "
"local")
settings('sslverify', 'true')
utils.settings('sslverify', 'true')
if scheme == 'https':
settings('https', 'true')
utils.settings('https', 'true')
else:
settings('https', 'false')
utils.settings('https', 'false')
# And finally do some logging
LOG.debug("Writing to Kodi user settings file")
LOG.debug("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s ",
server['machineIdentifier'], server['ip'], server['port'],
server['scheme'])
@staticmethod
def _add_sources(root, extension):
changed = False
count = 2
for source in root.findall('.//path'):
if source.text == extension:
count -= 1
if count == 0:
# sources already set
break
else:
# Missing smb:// occurences, re-add.
changed = True
for _ in range(0, count):
source = etree.SubElement(root, 'source')
etree.SubElement(
source,
'name').text = "PlexKodiConnect Masterlock Hack"
etree.SubElement(
source,
'path',
{'pathversion': "1"}).text = extension
etree.SubElement(source, 'allowsharing').text = "true"
return changed
def setup(self):
"""
Initial setup. Run once upon startup.
@ -469,18 +495,22 @@ class InitialSetup(object):
"""
LOG.info("Initial setup called.")
try:
with XmlKodiSetting('advancedsettings.xml',
force_create=True,
top_element='advancedsettings') as xml:
with utils.XmlKodiSetting('advancedsettings.xml',
force_create=True,
top_element='advancedsettings') as xml:
# Get current Kodi video cache setting
cache = xml.get_setting(['cache', 'memorysize'])
# Disable foreground "Loading media information from files"
# (still used by Kodi, even though the Wiki says otherwise)
xml.set_setting(['musiclibrary', 'backgroundupdate'],
value='true')
# Disable cleaning of library - not compatible with PKC
xml.set_setting(['videolibrary', 'cleanonupdate'],
value='false')
cleanonupdate = xml.get_setting(
['videolibrary', 'cleanonupdate']) == 'true'
if utils.settings('useDirectPaths') != '1':
# Disable cleaning of library - not compatible with PKC
# Only do this for add-on paths
xml.set_setting(['videolibrary', 'cleanonupdate'],
value='false')
# Set completely watched point same as plex (and not 92%)
xml.set_setting(['video', 'ignorepercentatend'], value='10')
xml.set_setting(['video', 'playcountminimumpercent'],
@ -488,161 +518,192 @@ class InitialSetup(object):
xml.set_setting(['video', 'ignoresecondsatstart'],
value='60')
reboot = xml.write_xml
except etree.ParseError:
except utils.ParseError:
cache = None
reboot = False
cleanonupdate = False
# Kodi default cache if no setting is set
cache = str(cache.text) if cache is not None else '20971520'
LOG.info('Current Kodi video memory cache in bytes: %s', cache)
settings('kodi_video_cache', value=cache)
utils.settings('kodi_video_cache', value=cache)
# Hack to make PKC Kodi master lock compatible
try:
with XmlKodiSetting('sources.xml',
force_create=True,
top_element='sources') as xml:
root = xml.set_setting(['video'])
count = 2
for source in root.findall('.//path'):
if source.text == "smb://":
count -= 1
if count == 0:
# sources already set
break
else:
# Missing smb:// occurences, re-add.
for _ in range(0, count):
source = etree.SubElement(root, 'source')
etree.SubElement(
source,
'name').text = "PlexKodiConnect Masterlock Hack"
etree.SubElement(
source,
'path',
attrib={'pathversion': "1"}).text = "smb://"
etree.SubElement(source, 'allowsharing').text = "true"
if reboot is False:
reboot = xml.write_xml
except etree.ParseError:
with utils.XmlKodiSetting('sources.xml',
force_create=True,
top_element='sources') as xml:
changed = False
for extension in ('smb://', 'nfs://'):
root = xml.set_setting(['video'])
changed = self._add_sources(root, extension) or changed
if changed:
xml.write_xml = True
reboot = True
except utils.ParseError:
pass
# Do we need to migrate stuff?
check_migration()
migration.check_migration()
# Reload the server IP cause we might've deleted it during migration
self.server = UserClient().get_server()
app.CONN.load()
# Display a warning if Kodi puts ALL movies into the queue, basically
# breaking playback reporting for PKC
if js.settings_getsettingvalue('videoplayer.autoplaynextitem'):
LOG.warn('Kodi setting videoplayer.autoplaynextitem is enabled!')
if settings('warned_setting_videoplayer.autoplaynextitem') == 'false':
warn = False
settings = js.settings_getsettingvalue('videoplayer.autoplaynextitem')
if v.KODIVERSION >= 18:
# Answer for videoplayer.autoplaynextitem:
# [{u'label': u'Music videos', u'value': 0},
# {u'label': u'TV shows', u'value': 1},
# {u'label': u'Episodes', u'value': 2},
# {u'label': u'Movies', u'value': 3},
# {u'label': u'Uncategorized', u'value': 4}]
if 1 in settings or 2 in settings or 3 in settings:
warn = True
else:
# Kodi Krypton: answer is boolean
if settings:
warn = True
if warn:
LOG.warn('Kodi setting videoplayer.autoplaynextitem is: %s',
settings)
if utils.settings('warned_setting_videoplayer.autoplaynextitem') == 'false':
# Only warn once
settings('warned_setting_videoplayer.autoplaynextitem',
value='true')
utils.settings('warned_setting_videoplayer.autoplaynextitem',
value='true')
# Warning: Kodi setting "Play next video automatically" is
# enabled. This could break PKC. Deactivate?
if dialog('yesno', lang(29999), lang(30003)):
js.settings_setsettingvalue('videoplayer.autoplaynextitem',
False)
if utils.yesno_dialog(utils.lang(29999), utils.lang(30003)):
if v.KODIVERSION >= 18:
for i in (1, 2, 3):
try:
settings.remove(i)
except ValueError:
pass
js.settings_setsettingvalue('videoplayer.autoplaynextitem',
settings)
else:
js.settings_setsettingvalue('videoplayer.autoplaynextitem',
False)
# Set any video library updates to happen in the background in order to
# hide "Compressing database"
js.settings_setsettingvalue('videolibrary.backgroundupdate', True)
# If a Plex server IP has already been set
# return only if the right machine identifier is found
if self.server:
LOG.info("PMS is already set: %s. Checking now...", self.server)
if app.CONN.server:
LOG.info("PMS is already set: %s. Checking now...", app.CONN.server)
if self.check_existing_pms():
LOG.info("Using PMS %s with machineIdentifier %s",
self.server, self.serverid)
_write_pms_settings(self.server, self.pms_token)
app.CONN.server, app.CONN.machine_identifier)
self.save_pms_settings(app.CONN.server, self.pms_token)
if utils.settings('kodi_db_has_been_wiped_clean') == 'false':
# If the user chose to go to the PKC settings on the first run
# Will trigger a reboot
utils.wipe_database()
if reboot is True:
reboot_kodi()
utils.reboot_kodi()
return
else:
LOG.info('No PMS set yet')
# If not already retrieved myplex info, optionally let user sign in
# to plex.tv. This DOES get called on very first install run
if not self.plex_token and self.myplexlogin:
if not self.plex_token and app.ACCOUNT.myplexlogin:
self.plex_tv_sign_in()
server = self.pick_pms()
server = self.pick_pms(inform_of_search=True)
if server is not None:
# Write our chosen server to Kodi settings file
self.save_pms_settings(server['baseURL'], server['token'])
self.write_pms_to_settings(server)
# User already answered the installation questions
if settings('InstallQuestionsAnswered') == 'true':
if utils.settings('InstallQuestionsAnswered') == 'true':
LOG.info('Installation questions already answered')
if utils.settings('kodi_db_has_been_wiped_clean') == 'false':
# If the user chose to go to the PKC settings on the first run
# Will trigger a reboot
utils.wipe_database()
if reboot is True:
reboot_kodi()
utils.reboot_kodi()
# Reload relevant settings
app.CONN.load()
app.ACCOUNT.load()
app.SYNC.load()
return
LOG.info('Showing install questions')
# Additional settings where the user needs to choose
# Direct paths (\\NAS\mymovie.mkv) or addon (http)?
goto_settings = False
if dialog('yesno',
lang(29999),
lang(39027),
lang(39028),
nolabel="Addon (Default)",
yeslabel="Native (Direct Paths)"):
from .windows import optionsdialog
# Use Add-on Paths (default, easy) or Direct Paths? PKC will not work
# if your Direct Paths setup is wrong!
# Buttons: Add-on Paths // Direct Paths
if optionsdialog.show(utils.lang(29999), utils.lang(39080),
utils.lang(39081), utils.lang(39082)) == 1:
LOG.debug("User opted to use direct paths.")
settings('useDirectPaths', value="1")
state.DIRECT_PATHS = True
utils.settings('useDirectPaths', value="1")
if cleanonupdate:
# Re-enable cleanonupdate
with utils.XmlKodiSetting('advancedsettings.xml') as xml:
xml.set_setting(['videolibrary', 'cleanonupdate'],
value='true')
# Are you on a system where you would like to replace paths
# \\NAS\mymovie.mkv with smb://NAS/mymovie.mkv? (e.g. Windows)
if dialog('yesno', heading=lang(29999), line1=lang(39033)):
if utils.yesno_dialog(utils.lang(29999), utils.lang(39033)):
LOG.debug("User chose to replace paths with smb")
else:
settings('replaceSMB', value="false")
utils.settings('replaceSMB', value="false")
# complete replace all original Plex library paths with custom SMB
if dialog('yesno', heading=lang(29999), line1=lang(39043)):
if utils.yesno_dialog(utils.lang(29999), utils.lang(39043)):
LOG.debug("User chose custom smb paths")
settings('remapSMB', value="true")
utils.settings('remapSMB', value="true")
# Please enter your custom smb paths in the settings under
# "Sync Options" and then restart Kodi
dialog('ok', heading=lang(29999), line1=lang(39044))
utils.messageDialog(utils.lang(29999), utils.lang(39044))
goto_settings = True
# Go to network credentials?
if dialog('yesno',
heading=lang(29999),
line1=lang(39029),
line2=lang(39030)):
if utils.yesno_dialog(utils.lang(39029), utils.lang(39030)):
LOG.debug("Presenting network credentials dialog.")
from utils import passwords_xml
passwords_xml()
from .windows import direct_path_sources
direct_path_sources.start()
# Disable Plex music?
if dialog('yesno', heading=lang(29999), line1=lang(39016)):
if utils.yesno_dialog(utils.lang(29999), utils.lang(39016)):
LOG.debug("User opted to disable Plex music library.")
settings('enableMusic', value="false")
utils.settings('enableMusic', value="false")
# Download additional art from FanArtTV
if dialog('yesno', heading=lang(29999), line1=lang(39061)):
if utils.yesno_dialog(utils.lang(29999), utils.lang(39061)):
LOG.debug("User opted to use FanArtTV")
settings('FanartTV', value="true")
utils.settings('FanartTV', value="true")
# Do you want to replace your custom user ratings with an indicator of
# how many versions of a media item you posses?
if dialog('yesno', heading=lang(29999), line1=lang(39718)):
if utils.yesno_dialog(utils.lang(29999), utils.lang(39718)):
LOG.debug("User opted to replace user ratings with version number")
settings('indicate_media_versions', value="true")
utils.settings('indicate_media_versions', value="true")
# If you use several Plex libraries of one kind, e.g. "Kids Movies" and
# "Parents Movies", be sure to check https://goo.gl/JFtQV9
# dialog.ok(heading=lang(29999), line1=lang(39076))
# dialog.ok(heading=utils.lang(29999), line1=utils.lang(39076))
# Need to tell about our image source for collections: themoviedb.org
# dialog.ok(heading=lang(29999), line1=lang(39717))
# dialog.ok(heading=utils.lang(29999), line1=utils.lang(39717))
# Make sure that we only ask these questions upon first installation
settings('InstallQuestionsAnswered', value='true')
utils.settings('InstallQuestionsAnswered', value='true')
if goto_settings is False:
# Open Settings page now? You will need to restart!
goto_settings = dialog('yesno',
heading=lang(29999),
line1=lang(39017))
goto_settings = utils.yesno_dialog(utils.lang(29999),
utils.lang(39017))
# New installation - make sure we start with a clean slate
utils.wipe_database(reboot=False)
if goto_settings:
state.PMS_STATUS = 'Stop'
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
elif reboot is True:
reboot_kodi()
LOG.info('User chose to go to the PKC settings - suspending PKC')
app.APP.stop_pkc = True
executebuiltin(
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
return
utils.reboot_kodi()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,30 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from .movies import Movie
from .tvshows import Show, Season, Episode
from .music import Artist, Album, Song
from .. import variables as v
# Note: always use same order of URL arguments, NOT urlencode:
# plex_id=<plex_id>&plex_type=<plex_type>&mode=play
ITEMTYPE_FROM_PLEXTYPE = {
v.PLEX_TYPE_MOVIE: Movie,
v.PLEX_TYPE_SHOW: Show,
v.PLEX_TYPE_SEASON: Season,
v.PLEX_TYPE_EPISODE: Episode,
v.PLEX_TYPE_ARTIST: Artist,
v.PLEX_TYPE_ALBUM: Album,
v.PLEX_TYPE_SONG: Song
}
ITEMTYPE_FROM_KODITYPE = {
v.KODI_TYPE_MOVIE: Movie,
v.KODI_TYPE_SHOW: Show,
v.KODI_TYPE_SEASON: Season,
v.KODI_TYPE_EPISODE: Episode,
v.KODI_TYPE_ARTIST: Artist,
v.KODI_TYPE_ALBUM: Album,
v.KODI_TYPE_SONG: Song
}

View file

@ -0,0 +1,171 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from ntpath import dirname
from ..plex_db import PlexDB, PLEXDB_LOCK
from ..kodi_db import KodiVideoDB, KODIDB_LOCK
from .. import db, timing, app
LOG = getLogger('PLEX.itemtypes.common')
# Note: always use same order of URL arguments, NOT urlencode:
# plex_id=<plex_id>&plex_type=<plex_type>&mode=play
def process_path(playurl):
"""
Do NOT use os.path since we have paths that might not apply to the current
OS!
"""
if '\\' in playurl:
# Local path
path = '%s\\' % playurl
toplevelpath = '%s\\' % dirname(dirname(path))
else:
# Network path
path = '%s/' % playurl
toplevelpath = '%s/' % dirname(dirname(path))
return path, toplevelpath
class ItemBase(object):
"""
Items to be called with "with Items() as xxx:" to ensure that __enter__
method is called (opens db connections)
Input:
kodiType: optional argument; e.g. 'video' or 'music'
"""
def __init__(self, last_sync, plexdb=None, kodidb=None, lock=True):
self.last_sync = last_sync
self.lock = lock
self.plexdb = plexdb
self.kodidb = kodidb
self.plexconn = plexdb.plexconn if plexdb else None
self.plexcursor = plexdb.cursor if plexdb else None
self.kodiconn = kodidb.kodiconn if kodidb else None
self.kodicursor = kodidb.cursor if kodidb else None
self.artconn = kodidb.artconn if kodidb else None
self.artcursor = kodidb.artcursor if kodidb else None
def __enter__(self):
"""
Open DB connections and cursors
"""
if self.lock:
PLEXDB_LOCK.acquire()
KODIDB_LOCK.acquire()
self.plexconn = db.connect('plex')
self.plexcursor = self.plexconn.cursor()
self.kodiconn = db.connect('video')
self.kodicursor = self.kodiconn.cursor()
self.artconn = db.connect('texture')
self.artcursor = self.artconn.cursor()
self.plexdb = PlexDB(plexconn=self.plexconn, lock=False)
self.kodidb = KodiVideoDB(texture_db=True,
kodiconn=self.kodiconn,
artconn=self.artconn,
lock=False)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""
Make sure DB changes are committed and connection to DB is closed.
"""
try:
if exc_type:
# re-raise any exception
return False
self.plexconn.commit()
self.kodiconn.commit()
if self.artconn:
self.artconn.commit()
return self
finally:
self.plexconn.close()
self.kodiconn.close()
if self.artconn:
self.artconn.close()
if self.lock:
PLEXDB_LOCK.release()
KODIDB_LOCK.release()
def commit(self):
self.plexconn.commit()
self.plexconn.execute('BEGIN')
self.kodiconn.commit()
self.kodiconn.execute('BEGIN')
if self.artconn:
self.artconn.commit()
self.artconn.execute('BEGIN')
def set_fanart(self, artworks, kodi_id, kodi_type):
"""
Writes artworks [dict containing only set artworks] to the Kodi art DB
"""
self.kodidb.modify_artwork(artworks,
kodi_id,
kodi_type)
def update_playstate(self, mark_played, view_count, resume, duration,
kodi_fileid, kodi_fileid_2, lastViewedAt):
"""
Use with websockets, not xml
"""
# If the playback was stopped, check whether we need to increment the
# playcount. PMS won't tell us the playcount via websockets
if mark_played:
LOG.info('Marking item as completely watched in Kodi')
try:
view_count += 1
except TypeError:
view_count = 1
resume = 0
# Do the actual update
self.kodidb.set_resume(kodi_fileid,
resume,
duration,
view_count,
timing.plex_date_to_kodi(lastViewedAt))
if kodi_fileid_2:
# Our dirty hack for episodes
self.kodidb.set_resume(kodi_fileid_2,
resume,
duration,
view_count,
timing.plex_date_to_kodi(lastViewedAt))
@staticmethod
def sync_this_item(section_id):
"""
Returns False if we are NOT synching the corresponding Plex library
with section_id [int] to Kodi or if this sections has not yet been
encountered by PKC
"""
return section_id in app.SYNC.section_ids
def update_provider_ids(self, api, kodi_id):
"""
Updates the unique metadata provider ids (such as the IMDB id). Returns
a dict of the Kodi unique ids
"""
# We might have an old provider id stored!
self.kodidb.remove_uniqueid(kodi_id, api.kodi_type)
return self.add_provider_ids(api, kodi_id)
def add_provider_ids(self, api, kodi_id):
"""
Adds the unique ids for all metadata providers to the Kodi database,
such as IMDB or The Movie Database TMDB.
Returns a dict of the Kodi ids: {<provider>: <kodi_unique_id>}
"""
kodi_unique_ids = api.guids.copy()
for provider, provider_id in api.guids.iteritems():
kodi_unique_ids[provider] = self.kodidb.add_uniqueid(
kodi_id,
api.kodi_type,
provider_id,
provider)
return kodi_unique_ids

View file

@ -0,0 +1,245 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .common import ItemBase
from ..plex_api import API
from .. import app, variables as v, plex_functions as PF
LOG = getLogger('PLEX.movies')
class Movie(ItemBase):
"""
Used for plex library-type movies
"""
def add_update(self, xml, section_name=None, section_id=None,
children=None):
"""
Process single movie
"""
api = API(xml)
if not self.sync_this_item(section_id or api.library_section_id()):
LOG.debug('Skipping sync of %s %s: %s - section %s not synched to '
'Kodi', api.plex_type, api.plex_id, api.title(),
section_id or api.library_section_id())
return
plex_id = api.plex_id
movie = self.plexdb.movie(plex_id)
if movie:
update_item = True
kodi_id = movie['kodi_id']
old_kodi_fileid = movie['kodi_fileid']
kodi_pathid = movie['kodi_pathid']
else:
update_item = False
kodi_id = self.kodidb.new_movie_id()
fullpath, path, filename = api.fullpath()
if app.SYNC.direct_paths and not fullpath.startswith('http'):
kodi_pathid = self.kodidb.add_path(path,
content='movies',
scraper='metadata.local')
else:
kodi_pathid = self.kodidb.get_path(path)
if update_item:
LOG.info('UPDATE movie plex_id: %s - %s', plex_id, api.title())
file_id = self.kodidb.modify_file(filename,
kodi_pathid,
api.date_created())
if file_id != old_kodi_fileid:
self.kodidb.remove_file(old_kodi_fileid)
rating_id = self.kodidb.update_ratings(kodi_id,
v.KODI_TYPE_MOVIE,
"default",
api.rating(),
api.votecount())
unique_id = self.update_provider_ids(api, kodi_id)
self.kodidb.modify_people(kodi_id,
v.KODI_TYPE_MOVIE,
api.people())
if app.SYNC.artwork:
self.kodidb.modify_artwork(api.artwork(),
kodi_id,
v.KODI_TYPE_MOVIE)
else:
LOG.info("ADD movie plex_id: %s - %s", plex_id, api.title())
file_id = self.kodidb.add_file(filename,
kodi_pathid,
api.date_created())
rating_id = self.kodidb.add_ratings(kodi_id,
v.KODI_TYPE_MOVIE,
"default",
api.rating(),
api.votecount())
unique_id = self.add_provider_ids(api, kodi_id)
self.kodidb.add_people(kodi_id,
v.KODI_TYPE_MOVIE,
api.people())
if app.SYNC.artwork:
self.kodidb.add_artwork(api.artwork(),
kodi_id,
v.KODI_TYPE_MOVIE)
unique_id = self._prioritize_provider_id(unique_id)
# Update Kodi's main entry
self.kodidb.add_movie(kodi_id,
file_id,
api.title(),
api.plot(),
api.shortplot(),
api.tagline(),
api.votecount(),
rating_id,
api.list_to_string(api.writers()),
api.year(),
unique_id,
api.sorttitle(),
api.runtime(),
api.content_rating(),
api.list_to_string(api.genres()),
api.list_to_string(api.directors()),
api.title(),
api.list_to_string(api.studios()),
api.trailer(),
api.list_to_string(api.countries()),
fullpath,
kodi_pathid,
api.premiere_date(),
api.userrating())
self.kodidb.modify_countries(kodi_id,
v.KODI_TYPE_MOVIE,
api.countries())
self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_MOVIE, api.genres())
self.kodidb.modify_streams(file_id, api.mediastreams(), api.runtime())
self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_MOVIE, api.studios())
tags = [section_name]
self._process_collections(api, tags, kodi_id, section_id, children)
self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_MOVIE, tags)
# Process playstate
self.kodidb.set_resume(file_id,
api.resume_point(),
api.runtime(),
api.viewcount(),
api.lastplayed())
self.plexdb.add_movie(plex_id=plex_id,
checksum=api.checksum(),
section_id=section_id,
kodi_id=kodi_id,
kodi_fileid=file_id,
kodi_pathid=kodi_pathid,
last_sync=self.last_sync)
def remove(self, plex_id, plex_type=None):
"""
Remove a movie with all references and all orphaned associated entries
from the Kodi DB
"""
movie = self.plexdb.movie(plex_id)
try:
kodi_id = movie['kodi_id']
file_id = movie['kodi_fileid']
kodi_type = v.KODI_TYPE_MOVIE
LOG.debug('Removing movie with plex_id %s, kodi_id: %s',
plex_id, kodi_id)
except TypeError:
LOG.error('Movie with plex_id %s not found - cannot delete',
plex_id)
return
# Remove the plex reference
self.plexdb.remove(plex_id, v.PLEX_TYPE_MOVIE)
# Remove artwork
self.kodidb.delete_artwork(kodi_id, kodi_type)
set_id = self.kodidb.get_set_id(kodi_id)
self.kodidb.modify_countries(kodi_id, kodi_type)
self.kodidb.modify_people(kodi_id, kodi_type)
self.kodidb.modify_genres(kodi_id, kodi_type)
self.kodidb.modify_studios(kodi_id, kodi_type)
self.kodidb.modify_tags(kodi_id, kodi_type)
# Delete kodi movie and file
self.kodidb.remove_file(file_id)
self.kodidb.remove_movie(kodi_id)
if set_id:
self.kodidb.delete_possibly_empty_set(set_id)
self.kodidb.remove_uniqueid(kodi_id, kodi_type)
self.kodidb.remove_ratings(kodi_id, kodi_type)
LOG.debug('Deleted movie %s from kodi database', plex_id)
def update_userdata(self, xml_element, plex_type):
"""
Updates the Kodi watched state of the item from PMS. Also retrieves
Plex resume points for movies in progress.
Returns True if successful, False otherwise (e.g. item missing)
"""
api = API(xml_element)
# Get key and db entry on the Kodi db side
db_item = self.plexdb.item_by_id(api.plex_id, plex_type)
if not db_item:
LOG.info('Item not yet synced: %s', xml_element.attrib)
return False
# Write to Kodi DB
self.kodidb.set_resume(db_item['kodi_fileid'],
api.resume_point(),
api.runtime(),
api.viewcount(),
api.lastplayed())
self.kodidb.update_userrating(db_item['kodi_id'],
db_item['kodi_type'],
api.userrating())
return True
def _process_collections(self, api, tags, kodi_id, section_id, children):
for _, set_name in api.collections():
tags.append(set_name)
for plex_set_id, set_name in api.collections():
set_api = None
# Add any sets from Plex collection tags
kodi_set_id = self.kodidb.create_collection(set_name)
self.kodidb.assign_collection(kodi_set_id, kodi_id)
if not app.SYNC.artwork:
# Rest below is to get collection artwork
# TODO: continue instead of break (see TODO/break below)
break
if children is None:
# e.g. when added via websocket
LOG.debug('Costly looking up Plex collection %s: %s',
plex_set_id, set_name)
for index, coll_plex_id in api.collections_match(section_id):
# Get Plex artwork for collections - a pain
if index == plex_set_id:
set_xml = PF.GetPlexMetadata(coll_plex_id)
try:
set_xml.attrib
except AttributeError:
LOG.error('Could not get set metadata %s',
coll_plex_id)
continue
set_api = API(set_xml[0])
break
elif plex_set_id in children:
# Provided by get_metadata thread
set_api = API(children[plex_set_id][0])
if set_api:
self.kodidb.modify_artwork(set_api.artwork(),
kodi_set_id,
v.KODI_TYPE_SET)
# TODO: Once Kodi (19?) supports SEVERAL sets/collections per
# movie, support that. For now, we only take the very first
# collection/set that Plex returns
break
@staticmethod
def _prioritize_provider_id(unique_ids):
"""
Prioritize which ID ends up in the SHOW table (there can only be 1)
tvdb > imdb > tmdb
"""
return unique_ids.get('imdb',
unique_ids.get('tmdb',
unique_ids.get('tvdb')))

View file

@ -0,0 +1,635 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .common import ItemBase
from ..plex_api import API
from ..plex_db import PlexDB, PLEXDB_LOCK
from ..kodi_db import KodiMusicDB, KODIDB_LOCK
from .. import plex_functions as PF, db, timing, app, variables as v
LOG = getLogger('PLEX.music')
class MusicMixin(object):
def __enter__(self):
"""
Overwrite to use the Kodi music DB instead of the video DB
"""
if self.lock:
PLEXDB_LOCK.acquire()
KODIDB_LOCK.acquire()
self.plexconn = db.connect('plex')
self.plexcursor = self.plexconn.cursor()
self.kodiconn = db.connect('music')
self.kodicursor = self.kodiconn.cursor()
self.artconn = db.connect('texture')
self.artcursor = self.artconn.cursor()
self.plexdb = PlexDB(plexconn=self.plexconn, lock=False)
self.kodidb = KodiMusicDB(texture_db=True,
kodiconn=self.kodiconn,
artconn=self.artconn,
lock=False)
return self
def update_userdata(self, xml_element, plex_type):
"""
Updates the Kodi watched state of the item from PMS. Also retrieves
Plex resume points for movies in progress.
Returns True if successful, False otherwise (e.g. item missing)
"""
api = API(xml_element)
# Get key and db entry on the Kodi db side
db_item = self.plexdb.item_by_id(api.plex_id, plex_type)
if not db_item:
LOG.info('Item not yet synced: %s', xml_element.attrib)
return False
# Grab the user's viewcount, resume points etc. from PMS' answer
self.kodidb.update_userrating(db_item['kodi_id'],
db_item['kodi_type'],
api.userrating())
if plex_type == v.PLEX_TYPE_SONG:
self.kodidb.set_playcount(api.viewcount(),
api.lastplayed(),
db_item['kodi_id'],)
return True
def remove(self, plex_id, plex_type=None):
"""
Remove the entire music object, including all associated entries from
both Plex and Kodi DBs
"""
db_item = self.plexdb.item_by_id(plex_id, plex_type)
if not db_item:
LOG.debug('Cannot delete plex_id %s - not found in DB', plex_id)
return
LOG.debug('Removing %s %s with kodi_id: %s',
db_item['plex_type'], plex_id, db_item['kodi_id'])
# Remove the plex reference
self.plexdb.remove(plex_id, db_item['plex_type'])
# SONG #####
if db_item['plex_type'] == v.PLEX_TYPE_SONG:
# Delete episode, verify season and tvshow
self.remove_song(db_item['kodi_id'], db_item['kodi_pathid'])
# Album verification
if not self.plexdb.album_has_songs(db_item['album_id']):
# No songleft for this album - so delete the album
self.remove_album(db_item['parent_id'])
self.plexdb.remove(db_item['album_id'], v.PLEX_TYPE_ALBUM)
# Artist verification
if (not self.plexdb.artist_has_albums(db_item['artist_id']) and
not self.plexdb.artist_has_songs(db_item['artist_id'])):
self.remove_artist(db_item['grandparent_id'])
self.plexdb.remove(db_item['artist_id'], v.PLEX_TYPE_ARTIST)
# ALBUM #####
elif db_item['plex_type'] == v.PLEX_TYPE_ALBUM:
# Remove songs, album, verify artist
songs = list(self.plexdb.song_by_album(db_item['plex_id']))
for song in songs:
self.remove_song(song['kodi_id'], song['kodi_pathid'])
self.plexdb.remove(song['plex_id'], v.PLEX_TYPE_SONG)
# Remove the album
self.remove_album(db_item['kodi_id'])
# Show verification
if (not self.plexdb.artist_has_albums(db_item['kodi_id']) and
not self.plexdb.artist_has_songs(db_item['kodi_id'])):
# There's no other album or song left, delete the artist
self.remove_artist(db_item['parent_id'])
self.plexdb.remove(db_item['artist_id'], v.KODI_TYPE_ARTIST)
# ARTIST #####
elif db_item['plex_type'] == v.PLEX_TYPE_ARTIST:
# Remove songs, albums and the artist himself
songs = list(self.plexdb.song_by_artist(db_item['plex_id']))
for song in songs:
self.remove_song(song['kodi_id'], song['kodi_pathid'])
self.plexdb.remove(song['plex_id'], v.PLEX_TYPE_SONG)
albums = list(self.plexdb.album_by_artist(db_item['plex_id']))
for album in albums:
self.remove_album(album['kodi_id'])
self.plexdb.remove(album['plex_id'], v.PLEX_TYPE_ALBUM)
self.remove_artist(db_item['kodi_id'])
LOG.debug('Deleted %s %s from all databases',
db_item['plex_type'], db_item['plex_id'])
def remove_song(self, kodi_id, path_id=None):
"""
Remove song, orphaned artists and orphaned paths
"""
if not path_id:
path_id = self.kodidb.path_id_from_song(kodi_id)
self.kodidb.delete_song_from_song_artist(kodi_id)
self.kodidb.remove_song(kodi_id)
# Check whether we have orphaned path entries
if not self.kodidb.path_id_from_song(kodi_id):
self.kodidb.remove_path(path_id)
if v.KODIVERSION < 18:
self.kodidb.remove_albuminfosong(kodi_id)
self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_SONG)
def remove_album(self, kodi_id):
'''
Remove an album
'''
if v.KODIVERSION < 18:
self.kodidb.delete_album_from_album_genre(kodi_id)
self.kodidb.remove_album(kodi_id)
self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_ALBUM)
def remove_artist(self, kodi_id):
'''
Remove an artist and associated songs and albums
'''
self.kodidb.remove_artist(kodi_id)
self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_ARTIST)
class Artist(MusicMixin, ItemBase):
"""
For Plex library-type artists
"""
def add_update(self, xml, section_name=None, section_id=None,
children=None):
"""
Process a single artist
"""
api = API(xml)
if not self.sync_this_item(section_id or api.library_section_id()):
LOG.debug('Skipping sync of %s %s: %s - section %s not synched to '
'Kodi', api.plex_type, api.plex_id, api.title(),
section_id or api.library_section_id())
return
plex_id = api.plex_id
artist = self.plexdb.artist(plex_id)
if not artist:
update_item = False
else:
update_item = True
kodi_id = artist['kodi_id']
# Not yet implemented by Plex
musicBrainzId = None
if app.SYNC.artwork:
artworks = api.artwork()
if 'poster' in artworks:
thumb = "<thumb>%s</thumb>" % artworks['poster']
else:
thumb = None
if 'fanart' in artworks:
fanart = "<fanart>%s</fanart>" % artworks['fanart']
else:
fanart = None
else:
thumb, fanart = None, None
# UPDATE THE ARTIST #####
if update_item:
LOG.info("UPDATE artist plex_id: %s - Name: %s", plex_id, api.title())
# OR ADD THE ARTIST #####
else:
LOG.info("ADD artist plex_id: %s - Name: %s", plex_id, api.title())
# safety checks: It looks like plex supports the same artist
# multiple times.
# Kodi doesn't allow that. In case that happens we just merge the
# artist entries.
kodi_id = self.kodidb.add_artist(api.title(), musicBrainzId)
self.kodidb.update_artist(api.list_to_string(api.genres()),
api.plot(),
thumb,
fanart,
timing.unix_date_to_kodi(self.last_sync),
kodi_id)
if app.SYNC.artwork:
self.kodidb.modify_artwork(artworks,
kodi_id,
v.KODI_TYPE_ARTIST)
self.plexdb.add_artist(plex_id,
api.checksum(),
section_id,
kodi_id,
self.last_sync)
class Album(MusicMixin, ItemBase):
def add_update(self, xml, section_name=None, section_id=None,
children=None, scan_children=True):
"""
Process a single album
scan_children: set to False if you don't want to add children, e.g. to
avoid infinite loops
"""
api = API(xml)
if not self.sync_this_item(section_id or api.library_section_id()):
LOG.debug('Skipping sync of %s %s: %s - section %s not synched to '
'Kodi', api.plex_type, api.plex_id, api.title(),
section_id or api.library_section_id())
return
plex_id = api.plex_id
album = self.plexdb.album(plex_id)
if album:
update_item = True
kodi_id = album['kodi_id']
else:
update_item = False
# Parent artist - should always be present
parent_id = api.parent_id()
artist = self.plexdb.artist(parent_id)
if not artist:
LOG.info('Artist %s does not yet exist in DB', parent_id)
artist_xml = PF.GetPlexMetadata(parent_id)
try:
artist_xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error('Could not get artist %s xml for %s',
parent_id, xml.attrib)
return
Artist(self.last_sync,
plexdb=self.plexdb,
kodidb=self.kodidb).add_update(artist_xml[0],
section_name,
section_id)
artist = self.plexdb.artist(parent_id)
if not artist:
LOG.error('Adding artist %s failed for %s',
parent_id, xml.attrib)
return
artist_id = artist['kodi_id']
# See if we have a compilation - Plex does NOT feature a compilation
# flag for albums
compilation = 0
if children is None:
LOG.info('No children songs passed, getting them')
children = PF.GetAllPlexChildren(plex_id)
try:
children[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error('Could not get children for Plex id %s', plex_id)
return
for song in children:
if song.get('originalTitle') is not None:
compilation = 1
break
name = api.title()
# Not yet implemented by Plex, let's use unique last.fm or gracenote
musicBrainzId = None
genre = api.list_to_string(api.genres())
if app.SYNC.artwork:
artworks = api.artwork()
if 'poster' in artworks:
thumb = "<thumb>%s</thumb>" % artworks['poster']
else:
thumb = None
else:
thumb = None
# UPDATE THE ALBUM #####
if update_item:
LOG.info("UPDATE album plex_id: %s - Name: %s", plex_id, name)
if v.KODIVERSION >= 18:
self.kodidb.update_album(name,
musicBrainzId,
api.artist_name(),
genre,
api.year(),
compilation,
api.plot(),
thumb,
api.list_to_string(api.studios()),
api.userrating(),
timing.unix_date_to_kodi(self.last_sync),
'album',
kodi_id)
else:
self.kodidb.update_album_17(name,
musicBrainzId,
api.artist_name(),
genre,
api.year(),
compilation,
api.plot(),
thumb,
api.list_to_string(api.studios()),
api.userrating(),
timing.unix_date_to_kodi(self.last_sync),
'album',
kodi_id)
# OR ADD THE ALBUM #####
else:
LOG.info("ADD album plex_id: %s - Name: %s", plex_id, name)
kodi_id = self.kodidb.new_album_id()
if v.KODIVERSION >= 18:
self.kodidb.add_album(kodi_id,
name,
musicBrainzId,
api.artist_name(),
genre,
api.year(),
compilation,
api.plot(),
thumb,
api.list_to_string(api.studios()),
api.userrating(),
timing.unix_date_to_kodi(self.last_sync),
'album')
else:
self.kodidb.add_album_17(kodi_id,
name,
musicBrainzId,
api.artist_name(),
genre,
api.year(),
compilation,
api.plot(),
thumb,
api.list_to_string(api.studios()),
api.userrating(),
timing.unix_date_to_kodi(self.last_sync),
'album')
self.kodidb.add_albumartist(artist_id, kodi_id, api.artist_name())
if app.SYNC.artwork:
self.kodidb.modify_artwork(artworks,
kodi_id,
v.KODI_TYPE_ALBUM)
self.plexdb.add_album(plex_id,
api.checksum(),
section_id,
artist_id,
parent_id,
kodi_id,
self.last_sync)
# Add all children - all tracks
if scan_children:
context = Song(self.last_sync,
plexdb=self.plexdb,
kodidb=self.kodidb)
for song in children:
context.add_update(song,
section_name=section_name,
section_id=section_id,
album_xml=xml,
genres=api.genres(),
genre=genre,
compilation=compilation)
class Song(MusicMixin, ItemBase):
def add_update(self, xml, section_name=None, section_id=None,
children=None, album_xml=None, genres=None, genre=None,
compilation=None):
"""
Process single song/track
"""
api = API(xml)
if not self.sync_this_item(section_id or api.library_section_id()):
LOG.debug('Skipping sync of %s %s: %s - section %s not synched to '
'Kodi', api.plex_type, api.plex_id, api.title(),
section_id or api.library_section_id())
return
plex_id = api.plex_id
song = self.plexdb.song(plex_id)
if song:
update_item = True
kodi_id = song['kodi_id']
kodi_pathid = song['kodi_pathid']
else:
update_item = False
kodi_id = self.kodidb.add_song_id()
artist_id = api.grandparent_id()
album_id = api.parent_id()
# The grandparent Artist - should always be present for every song!
artist = self.plexdb.artist(artist_id)
if not artist:
LOG.warn('Grandparent artist %s not found in DB, adding it',
artist_id)
artist_xml = PF.GetPlexMetadata(artist_id)
try:
artist_xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error('Grandparent tvartist %s xml download failed for %s',
artist_id, xml.attrib)
return
Artist(self.last_sync,
plexdb=self.plexdb,
kodidb=self.kodidb).add_update(artist_xml[0],
section_name,
section_id)
artist = self.plexdb.artist(artist_id)
if not artist:
LOG.error('Still could not find grandparent artist %s for %s',
artist_id, xml.attrib)
return
grandparent_id = artist['kodi_id']
# The parent Album
if not album_id:
# No album found, create a single's album
LOG.info('Creating singles album')
parent_id = self.kodidb.new_album_id()
if v.KODIVERSION >= 18:
self.kodidb.add_album(kodi_id,
None,
None,
None,
genre,
api.year(),
None,
None,
None,
None,
None,
timing.unix_date_to_kodi(self.last_sync),
'single')
else:
self.kodidb.add_album_17(kodi_id,
None,
None,
None,
genre,
api.year(),
None,
None,
None,
None,
None,
timing.unix_date_to_kodi(self.last_sync),
'single')
else:
album = self.plexdb.album(album_id)
if not album:
LOG.warn('Parent album %s not found in DB, adding it', album_id)
album_xml = PF.GetPlexMetadata(album_id)
try:
album_xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error('Parent album %s xml download failed for %s',
album_id, xml.attrib)
return
Album(self.last_sync,
plexdb=self.plexdb,
kodidb=self.kodidb).add_update(album_xml[0],
section_name,
section_id,
children=[xml],
scan_children=False)
album = self.plexdb.album(album_id)
if not album:
LOG.error('Still could not find parent album %s for %s',
album_id, xml.attrib)
return
parent_id = album['kodi_id']
title = api.title()
# Not yet implemented by Plex
musicBrainzId = None
comment = None
# Getting artists name is complicated
if compilation is not None:
if compilation == 0:
artists = api.grandparent_title()
else:
artists = xml.get('originalTitle')
else:
# compilation not set
artists = xml.get('originalTitle', api.grandparent_title())
tracknumber = api.index() or 0
disc = api.disc_number() or 1
if disc == 1:
track = tracknumber
else:
track = disc * 2 ** 16 + tracknumber
year = api.year()
if not year and album_xml is not None:
# Plex did not pass year info - get it from the parent album
album_api = API(album_xml)
year = album_api.year()
moods = []
for entry in xml:
if entry.tag == 'Mood':
moods.append(entry.attrib['tag'])
mood = api.list_to_string(moods)
_, path, filename = api.fullpath()
# UPDATE THE SONG #####
if update_item:
LOG.info("UPDATE song plex_id: %s - %s", plex_id, title)
# Use dummy strHash '123' for Kodi
self.kodidb.update_path(path, kodi_pathid)
# Update the song entry
if v.KODIVERSION >= 18:
# Kodi Leia
self.kodidb.update_song(parent_id,
artists,
genre,
title,
track,
api.runtime(),
year,
filename,
api.viewcount(),
api.lastplayed(),
api.userrating(),
comment,
mood,
api.date_created(),
kodi_id)
else:
self.kodidb.update_song_17(parent_id,
artists,
genre,
title,
track,
api.runtime(),
year,
filename,
api.viewcount(),
api.lastplayed(),
api.userrating(),
comment,
mood,
api.date_created(),
kodi_id)
# OR ADD THE SONG #####
else:
LOG.info("ADD song plex_id: %s - %s", plex_id, title)
# Add path
kodi_pathid = self.kodidb.add_path(path)
# Create the song entry
if v.KODIVERSION >= 18:
# Kodi Leia
self.kodidb.add_song(kodi_id,
parent_id,
kodi_pathid,
artists,
genre,
title,
track,
api.runtime(),
year,
filename,
musicBrainzId,
api.viewcount(),
api.lastplayed(),
api.userrating(),
0,
0,
mood,
api.date_created())
else:
self.kodidb.add_song_17(kodi_id,
parent_id,
kodi_pathid,
artists,
genre,
title,
track,
api.runtime(),
year,
filename,
musicBrainzId,
api.viewcount(),
api.lastplayed(),
api.userrating(),
0,
0,
mood,
api.date_created())
if v.KODIVERSION < 18:
# Link song to album
self.kodidb.add_albuminfosong(kodi_id,
parent_id,
track,
title,
api.runtime())
# Link song to artists
artist_name = api.grandparent_title()
# Do the actual linking
self.kodidb.add_song_artist(grandparent_id, kodi_id, artist_name)
# Add genres
if genres:
self.kodidb.add_music_genres(kodi_id, genres, v.KODI_TYPE_SONG)
if app.SYNC.artwork:
artworks = api.artwork()
self.kodidb.modify_artwork(artworks,
kodi_id,
v.KODI_TYPE_SONG)
if xml.get('parentKey') is None:
# Update album artwork
self.kodidb.modify_artwork(artworks,
parent_id,
v.KODI_TYPE_ALBUM)
self.plexdb.add_song(plex_id,
api.checksum(),
section_id,
artist_id,
grandparent_id,
album_id,
parent_id,
kodi_id,
kodi_pathid,
self.last_sync)

View file

@ -0,0 +1,594 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .common import ItemBase, process_path
from ..plex_api import API
from .. import plex_functions as PF, app, variables as v
LOG = getLogger('PLEX.tvshows')
class TvShowMixin(object):
def update_userdata(self, xml_element, plex_type):
"""
Updates the Kodi watched state of the item from PMS. Also retrieves
Plex resume points for movies in progress.
"""
api = API(xml_element)
# Get key and db entry on the Kodi db side
db_item = self.plexdb.item_by_id(api.plex_id, plex_type)
if not db_item:
LOG.info('Item not yet synced: %s', xml_element.attrib)
return False
# Grab the user's viewcount, resume points etc. from PMS' answer
self.kodidb.update_userrating(db_item['kodi_id'],
db_item['kodi_type'],
api.userrating())
if plex_type == v.PLEX_TYPE_EPISODE:
self.kodidb.set_resume(db_item['kodi_fileid'],
api.resume_point(),
api.runtime(),
api.viewcount(),
api.lastplayed())
if db_item['kodi_fileid_2']:
self.kodidb.set_resume(db_item['kodi_fileid_2'],
api.resume_point(),
api.runtime(),
api.viewcount(),
api.lastplayed())
return True
def remove(self, plex_id, plex_type=None):
"""
Remove the entire TV shows object (show, season or episode) including
all associated entries from the Kodi DB.
"""
db_item = self.plexdb.item_by_id(plex_id, plex_type)
if not db_item:
LOG.debug('Cannot delete plex_id %s - not found in DB', plex_id)
return
LOG.debug('Removing %s %s with kodi_id: %s',
db_item['plex_type'], plex_id, db_item['kodi_id'])
# Remove the plex reference
self.plexdb.remove(plex_id, db_item['plex_type'])
# EPISODE #####
if db_item['plex_type'] == v.PLEX_TYPE_EPISODE:
# Delete episode, verify season and tvshow
self.remove_episode(db_item)
# Season verification
if (db_item['season_id'] and
not self.plexdb.season_has_episodes(db_item['season_id'])):
# No episode left for this season - so delete the season
self.remove_season(db_item['parent_id'])
self.plexdb.remove(db_item['season_id'], v.PLEX_TYPE_SEASON)
# Show verification
if (not self.plexdb.show_has_seasons(db_item['show_id']) and
not self.plexdb.show_has_episodes(db_item['show_id'])):
# No seasons for show left - so delete entire show
self.remove_show(db_item['grandparent_id'])
self.plexdb.remove(db_item['show_id'], v.PLEX_TYPE_SHOW)
# SEASON #####
elif db_item['plex_type'] == v.PLEX_TYPE_SEASON:
# Remove episodes, season, verify tvshow
episodes = list(self.plexdb.episode_by_season(db_item['plex_id']))
for episode in episodes:
self.remove_episode(episode)
self.plexdb.remove(episode['plex_id'], v.PLEX_TYPE_EPISODE)
# Remove season
self.remove_season(db_item['kodi_id'])
# Show verification
if (not self.plexdb.show_has_seasons(db_item['show_id']) and
not self.plexdb.show_has_episodes(db_item['show_id'])):
# There's no other season or episode left, delete the show
self.remove_show(db_item['parent_id'])
self.plexdb.remove(db_item['show_id'], v.PLEX_TYPE_SHOW)
# TVSHOW #####
elif db_item['plex_type'] == v.PLEX_TYPE_SHOW:
# Remove episodes, seasons and the tvshow itself
seasons = list(self.plexdb.season_by_show(db_item['plex_id']))
for season in seasons:
self.remove_season(season['kodi_id'])
self.plexdb.remove(season['plex_id'], v.PLEX_TYPE_SEASON)
episodes = list(self.plexdb.episode_by_show(db_item['plex_id']))
for episode in episodes:
self.remove_episode(episode)
self.plexdb.remove(episode['plex_id'], v.PLEX_TYPE_EPISODE)
self.remove_show(db_item['kodi_id'])
LOG.debug('Deleted %s %s from all databases',
db_item['plex_type'], db_item['plex_id'])
def remove_show(self, kodi_id):
"""
Remove a TV show, and only the show, no seasons or episodes
"""
self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.remove_show(kodi_id)
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.remove_ratings(kodi_id, v.KODI_TYPE_SHOW)
LOG.debug("Removed tvshow: %s", kodi_id)
def remove_season(self, kodi_id):
"""
Remove a season, and only a season, not the show or episodes
"""
self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_SEASON)
self.kodidb.remove_season(kodi_id)
LOG.debug("Removed season: %s", kodi_id)
def remove_episode(self, db_item):
"""
Remove an episode, and episode only from the Kodi DB (not Plex DB)
"""
self.kodidb.modify_people(db_item['kodi_id'], v.KODI_TYPE_EPISODE)
self.kodidb.remove_file(db_item['kodi_fileid'])
if db_item['kodi_fileid_2']:
self.kodidb.remove_file(db_item['kodi_fileid_2'])
self.kodidb.delete_artwork(db_item['kodi_id'], v.KODI_TYPE_EPISODE)
self.kodidb.remove_episode(db_item['kodi_id'])
self.kodidb.remove_uniqueid(db_item['kodi_id'], v.KODI_TYPE_EPISODE)
self.kodidb.remove_ratings(db_item['kodi_id'], v.KODI_TYPE_EPISODE)
LOG.debug("Removed episode: %s", db_item['kodi_id'])
class Show(TvShowMixin, ItemBase):
"""
For Plex library-type TV shows
"""
def add_update(self, xml, section_name=None, section_id=None,
children=None):
"""
Process a single show
"""
api = API(xml)
if not self.sync_this_item(section_id or api.library_section_id()):
LOG.debug('Skipping sync of %s %s: %s - section %s not synched to '
'Kodi', api.plex_type, api.plex_id, api.title(),
section_id or api.library_section_id())
return
plex_id = api.plex_id
show = self.plexdb.show(plex_id)
if not show:
update_item = False
kodi_id = self.kodidb.new_show_id()
else:
update_item = True
kodi_id = show['kodi_id']
kodi_pathid = show['kodi_pathid']
# GET THE FILE AND PATH #####
if app.SYNC.direct_paths:
# Direct paths is set the Kodi way
playurl = api.validate_playurl(api.tv_show_path(),
api.plex_type,
folder=True)
if playurl is None:
return
path, toplevelpath = process_path(playurl)
toppathid = self.kodidb.add_path(toplevelpath,
content='tvshows',
scraper='metadata.local')
else:
# Set plugin path
toplevelpath = "plugin://%s.tvshows/" % v.ADDON_ID
path = "%s%s/" % (toplevelpath, plex_id)
# Do NOT set a parent id because addon-path cannot be "stacked"
toppathid = None
kodi_pathid = self.kodidb.add_path(path,
date_added=api.date_created(),
id_parent_path=toppathid)
# UPDATE THE TVSHOW #####
if update_item:
LOG.info("UPDATE tvshow plex_id: %s - %s", plex_id, api.title())
# update new ratings Kodi 17
rating_id = self.kodidb.update_ratings(kodi_id,
v.KODI_TYPE_SHOW,
"default",
api.rating(),
api.votecount())
unique_id = self._prioritize_provider_id(
self.update_provider_ids(api, kodi_id))
self.kodidb.modify_people(kodi_id,
v.KODI_TYPE_SHOW,
api.people())
if app.SYNC.artwork:
self.kodidb.modify_artwork(api.artwork(),
kodi_id,
v.KODI_TYPE_SHOW)
# Update the tvshow entry
self.kodidb.update_show(api.title(),
api.plot(),
rating_id,
api.premiere_date(),
api.list_to_string(api.genres()),
api.title(),
unique_id,
api.content_rating(),
api.list_to_string(api.studios()),
api.sorttitle(),
kodi_id)
# OR ADD THE TVSHOW #####
else:
LOG.info("ADD tvshow plex_id: %s - %s", plex_id, api.title())
# Link the path
self.kodidb.add_showlinkpath(kodi_id, kodi_pathid)
rating_id = self.kodidb.add_ratings(kodi_id,
v.KODI_TYPE_SHOW,
"default",
api.rating(),
api.votecount())
unique_id = self._prioritize_provider_id(
self.add_provider_ids(api, kodi_id))
self.kodidb.add_people(kodi_id,
v.KODI_TYPE_SHOW,
api.people())
if app.SYNC.artwork:
self.kodidb.add_artwork(api.artwork(),
kodi_id,
v.KODI_TYPE_SHOW)
# Create the tvshow entry
self.kodidb.add_show(kodi_id,
api.title(),
api.plot(),
rating_id,
api.premiere_date(),
api.list_to_string(api.genres()),
api.title(),
unique_id,
api.content_rating(),
api.list_to_string(api.studios()),
api.sorttitle())
self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_SHOW, api.genres())
# Process studios
self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_SHOW, api.studios())
# Process tags: view, PMS collection tags
tags = [section_name]
tags.extend([i for _, i in api.collections()])
self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags)
self.plexdb.add_show(plex_id=plex_id,
checksum=api.checksum(),
section_id=section_id,
kodi_id=kodi_id,
kodi_pathid=kodi_pathid,
last_sync=self.last_sync)
@staticmethod
def _prioritize_provider_id(unique_ids):
"""
Prioritize which ID ends up in the SHOW table (there can only be 1)
tvdb > imdb > tmdb
"""
return unique_ids.get('tvdb',
unique_ids.get('imdb',
unique_ids.get('tmdb')))
class Season(TvShowMixin, ItemBase):
def add_update(self, xml, section_name=None, section_id=None,
children=None):
"""
Process a single season of a certain tv show
"""
api = API(xml)
if not self.sync_this_item(section_id or api.library_section_id()):
LOG.debug('Skipping sync of %s %s: %s - section %s not synched to '
'Kodi', api.plex_type, api.plex_id, api.season_name(),
section_id or api.library_section_id())
return
plex_id = api.plex_id
season = self.plexdb.season(plex_id)
if not season:
update_item = False
else:
update_item = True
show_id = api.parent_id()
show = self.plexdb.show(show_id)
if not show:
LOG.warn('Parent TV show %s not found in DB, adding it', show_id)
show_xml = PF.GetPlexMetadata(show_id)
try:
show_xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error("Parent tvshow %s xml download failed", show_id)
return False
Show(self.last_sync,
plexdb=self.plexdb,
kodidb=self.kodidb).add_update(show_xml[0],
section_name,
section_id)
show = self.plexdb.show(show_id)
if not show:
LOG.error('Still could not find parent tv show %s', show_id)
return
parent_id = show['kodi_id']
if app.SYNC.artwork:
parent_artwork = api.artwork(kodi_id=parent_id,
kodi_type=v.KODI_TYPE_SHOW)
artwork = api.artwork()
# Remove all artwork that is identical for the season's show
for key in parent_artwork:
if key in artwork and artwork[key] == parent_artwork[key]:
del artwork[key]
if update_item:
LOG.info('UPDATE season plex_id %s - %s',
plex_id, api.season_name())
kodi_id = season['kodi_id']
self.kodidb.update_season(kodi_id,
parent_id,
api.index(),
api.season_name(),
api.userrating() or None)
if app.SYNC.artwork:
self.kodidb.modify_artwork(artwork,
kodi_id,
v.KODI_TYPE_SEASON)
else:
LOG.info('ADD season plex_id %s - %s', plex_id, api.season_name())
kodi_id = self.kodidb.add_season(parent_id,
api.index(),
api.season_name(),
api.userrating() or None)
if app.SYNC.artwork:
self.kodidb.add_artwork(artwork,
kodi_id,
v.KODI_TYPE_SEASON)
self.plexdb.add_season(plex_id=plex_id,
checksum=api.checksum(),
section_id=section_id,
show_id=show_id,
parent_id=parent_id,
kodi_id=kodi_id,
last_sync=self.last_sync)
class Episode(TvShowMixin, ItemBase):
def add_update(self, xml, section_name=None, section_id=None,
children=None):
"""
Process single episode
"""
api = API(xml)
if not self.sync_this_item(section_id or api.library_section_id()):
LOG.debug('Skipping sync of %s %s: %s - section %s not synched to '
'Kodi', api.plex_type, api.plex_id, api.title(),
section_id or api.library_section_id())
return
plex_id = api.plex_id
episode = self.plexdb.episode(plex_id)
if not episode:
update_item = False
kodi_id = self.kodidb.new_episode_id()
else:
update_item = True
kodi_id = episode['kodi_id']
old_kodi_fileid = episode['kodi_fileid']
old_kodi_fileid_2 = episode['kodi_fileid_2']
kodi_pathid = episode['kodi_pathid']
airs_before_season = "-1"
airs_before_episode = "-1"
# The grandparent TV show
show = self.plexdb.show(api.show_id())
if not show:
LOG.warn('Grandparent TV show %s not found in DB, adding it', api.show_id())
show_xml = PF.GetPlexMetadata(api.show_id())
try:
show_xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error("Grandparent tvshow %s xml download failed", api.show_id())
return False
Show(self.last_sync,
plexdb=self.plexdb,
kodidb=self.kodidb).add_update(show_xml[0],
section_name,
section_id)
show = self.plexdb.show(api.show_id())
if not show:
LOG.error('Still could not find grandparent tv show %s', api.show_id())
return
grandparent_id = show['kodi_id']
# The parent Season
season = self.plexdb.season(api.season_id())
if not season and api.season_id():
LOG.warn('Parent season %s not found in DB, adding it', api.season_id())
season_xml = PF.GetPlexMetadata(api.season_id())
try:
season_xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error("Parent season %s xml download failed", api.season_id())
return False
Season(self.last_sync,
plexdb=self.plexdb,
kodidb=self.kodidb).add_update(season_xml[0],
section_name,
section_id)
season = self.plexdb.season(api.season_id())
if not season:
LOG.error('Still could not find parent season %s', api.season_id())
return
parent_id = season['kodi_id'] if season else None
fullpath, path, filename = api.fullpath()
if app.SYNC.direct_paths and not fullpath.startswith('http'):
parent_path_id = self.kodidb.parent_path_id(path)
kodi_pathid = self.kodidb.add_path(path,
id_parent_path=parent_path_id)
else:
# Root path tvshows/ already saved in Kodi DB
kodi_pathid = self.kodidb.add_path(path)
# need to set a 2nd file entry for a path without plex show id
# This fixes e.g. context menu and widgets working as they
# should
# A dirty hack, really
path_2 = 'plugin://%s.tvshows/' % v.ADDON_ID
# filename_2 is exactly the same as filename
# so WITH plex show id!
kodi_pathid_2 = self.kodidb.add_path(path_2)
# UPDATE THE EPISODE #####
if update_item:
LOG.info("UPDATE episode plex_id: %s - %s", plex_id, api.title())
kodi_fileid = self.kodidb.modify_file(filename,
kodi_pathid,
api.date_created())
if not app.SYNC.direct_paths:
kodi_fileid_2 = self.kodidb.modify_file(filename,
kodi_pathid_2,
api.date_created())
else:
kodi_fileid_2 = None
if kodi_fileid != old_kodi_fileid:
self.kodidb.remove_file(old_kodi_fileid)
if not app.SYNC.direct_paths:
self.kodidb.remove_file(old_kodi_fileid_2)
ratingid = self.kodidb.update_ratings(kodi_id,
v.KODI_TYPE_EPISODE,
"default",
api.rating(),
api.votecount())
unique_id = self._prioritize_provider_id(
self.update_provider_ids(api, kodi_id))
self.kodidb.modify_people(kodi_id,
v.KODI_TYPE_EPISODE,
api.people())
if app.SYNC.artwork:
self.kodidb.modify_artwork(api.artwork(),
kodi_id,
v.KODI_TYPE_EPISODE)
self.kodidb.update_episode(api.title(),
api.plot(),
ratingid,
api.list_to_string(api.writers()),
api.premiere_date(),
api.runtime(),
api.list_to_string(api.directors()),
api.season_number(),
api.index(),
api.title(),
airs_before_season,
airs_before_episode,
fullpath,
kodi_pathid,
unique_id,
kodi_fileid, # and NOT kodi_fileid_2
parent_id,
api.userrating(),
kodi_id)
self.kodidb.set_resume(kodi_fileid,
api.resume_point(),
api.runtime(),
api.viewcount(),
api.lastplayed())
if not app.SYNC.direct_paths:
self.kodidb.set_resume(kodi_fileid_2,
api.resume_point(),
api.runtime(),
api.viewcount(),
api.lastplayed())
self.plexdb.add_episode(plex_id=plex_id,
checksum=api.checksum(),
section_id=section_id,
show_id=api.show_id(),
grandparent_id=grandparent_id,
season_id=api.season_id(),
parent_id=parent_id,
kodi_id=kodi_id,
kodi_fileid=kodi_fileid,
kodi_fileid_2=kodi_fileid_2,
kodi_pathid=kodi_pathid,
last_sync=self.last_sync)
# OR ADD THE EPISODE #####
else:
LOG.info("ADD episode plex_id: %s - %s", plex_id, api.title())
kodi_fileid = self.kodidb.add_file(filename,
kodi_pathid,
api.date_created())
if not app.SYNC.direct_paths:
kodi_fileid_2 = self.kodidb.add_file(filename,
kodi_pathid_2,
api.date_created())
else:
kodi_fileid_2 = None
rating_id = self.kodidb.add_ratings(kodi_id,
v.KODI_TYPE_EPISODE,
"default",
api.rating(),
api.votecount())
unique_id = self._prioritize_provider_id(
self.add_provider_ids(api, kodi_id))
self.kodidb.add_people(kodi_id,
v.KODI_TYPE_EPISODE,
api.people())
if app.SYNC.artwork:
self.kodidb.add_artwork(api.artwork(),
kodi_id,
v.KODI_TYPE_EPISODE)
self.kodidb.add_episode(kodi_id,
kodi_fileid, # and NOT kodi_fileid_2
api.title(),
api.plot(),
rating_id,
api.list_to_string(api.writers()),
api.premiere_date(),
api.runtime(),
api.list_to_string(api.directors()),
api.season_number(),
api.index(),
api.title(),
grandparent_id,
airs_before_season,
airs_before_episode,
fullpath,
kodi_pathid,
unique_id,
parent_id,
api.userrating())
self.kodidb.set_resume(kodi_fileid,
api.resume_point(),
api.runtime(),
api.viewcount(),
api.lastplayed())
if not app.SYNC.direct_paths:
self.kodidb.set_resume(kodi_fileid_2,
api.resume_point(),
api.runtime(),
api.viewcount(),
api.lastplayed())
self.plexdb.add_episode(plex_id=plex_id,
checksum=api.checksum(),
section_id=section_id,
show_id=api.show_id(),
grandparent_id=grandparent_id,
season_id=api.season_id(),
parent_id=parent_id,
kodi_id=kodi_id,
kodi_fileid=kodi_fileid,
kodi_fileid_2=kodi_fileid_2,
kodi_pathid=kodi_pathid,
last_sync=self.last_sync)
self.kodidb.modify_streams(kodi_fileid, # and NOT kodi_fileid_2
api.mediastreams(),
api.runtime())
@staticmethod
def _prioritize_provider_id(unique_ids):
"""
Prioritize which ID ends up in the SHOW table (there can only be 1)
tvdb > imdb > tmdb
"""
return unique_ids.get('tvdb',
unique_ids.get('imdb',
unique_ids.get('tmdb')))

View file

@ -1,11 +1,34 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Collection of functions using the Kodi JSON RPC interface.
See http://kodi.wiki/view/JSON-RPC_API
"""
from __future__ import absolute_import, division, unicode_literals
from json import loads, dumps
from utils import millis_to_kodi_time
from xbmc import executeJSONRPC
from . import kodi_constants, timing, variables as v
JSON_FROM_KODITYPE = {
v.KODI_TYPE_MOVIE: ('VideoLibrary.GetMovieDetails',
kodi_constants.FIELDS_MOVIES),
v.KODI_TYPE_SHOW: ('VideoLibrary.GetTVShowDetails',
kodi_constants.FIELDS_TVSHOWS),
v.KODI_TYPE_SEASON: ('VideoLibrary.GetSeasonDetails',
kodi_constants.FIELDS_SEASON),
v.KODI_TYPE_EPISODE: ('VideoLibrary.GetEpisodeDetails',
kodi_constants.FIELDS_EPISODES),
v.KODI_TYPE_ARTIST: ('AudioLibrary.GetArtistDetails',
kodi_constants.FIELDS_ARTISTS),
v.KODI_TYPE_ALBUM: ('AudioLibrary.GetAlbumDetails',
kodi_constants.FIELDS_ALBUMS),
v.KODI_TYPE_SONG: ('AudioLibrary.GetSongDetails',
kodi_constants.FIELDS_SONGS),
v.KODI_TYPE_SET: ('VideoLibrary.GetMovieSetDetails',
[]),
}
class JsonRPC(object):
"""
@ -147,12 +170,12 @@ def stop():
def seek_to(offset):
"""
Seeks all Kodi players to offset [int]
Seeks all Kodi players to offset [int] in milliseconds
"""
for playerid in get_player_ids():
JsonRPC("Player.Seek").execute(
return JsonRPC("Player.Seek").execute(
{"playerid": playerid,
"value": millis_to_kodi_time(offset)})
"value": timing.millis_to_kodi_time(offset)})
def smallforward():
@ -398,6 +421,41 @@ def get_item(playerid):
'properties': ['title', 'file']})['result']['item']
def get_current_audio_stream_index(playerid):
"""
Returns the currently active audio stream index [int]
"""
return JsonRPC('Player.GetProperties').execute({
'playerid': playerid,
'properties': ['currentaudiostream']})['result']['currentaudiostream']['index']
def get_current_subtitle_stream_index(playerid):
"""
Returns the currently active subtitle stream index [int] or None if there
are no subs
PICKING UP CHANGES ON SUBTITLES IS CURRENTLY BROKEN ON THE KODI SIDE! The
JSON reply won't change even though subtitles are changed :-(
"""
try:
return JsonRPC('Player.GetProperties').execute({
'playerid': playerid,
'properties': ['currentsubtitle', ]})['result']['currentsubtitle']['index']
except KeyError:
pass
def get_subtitle_enabled(playerid):
"""
Returns True if a subtitle is currently enabled, False otherwise.
PICKING UP CHANGES ON SUBTITLES IS CURRENTLY BROKEN ON THE KODI SIDE! The
JSON reply won't change even though subtitles are changed :-(
"""
return JsonRPC('Player.GetProperties').execute({
'playerid': playerid,
'properties': ['subtitleenabled', ]})['result']['subtitleenabled']
def get_player_props(playerid):
"""
Returns a dict for the active Kodi player with the following values:
@ -553,3 +611,16 @@ def settings_setsettingvalue(setting, value):
'setting': setting,
'value': value
})
def item_details(kodi_id, kodi_type):
'''
Returns the Kodi item dict for this item
'''
json, fields = JSON_FROM_KODITYPE[kodi_type]
ret = JsonRPC(json).execute({'%sid' % kodi_type: kodi_id,
'properties': fields})
try:
return ret['result']['%sdetails' % kodi_type]
except (KeyError, TypeError):
return {}

View file

@ -0,0 +1,92 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
script.module.metadatautils
kodi_constants.py
Several common constants for use with Kodi json api
'''
FIELDS_BASE = ['dateadded', 'file', 'lastplayed', 'plot', 'title', 'art',
'playcount']
FIELDS_FILE = FIELDS_BASE + ['streamdetails', 'director', 'resume', 'runtime']
FIELDS_MOVIES = FIELDS_FILE + ['plotoutline', 'sorttitle', 'cast', 'votes',
'showlink', 'top250', 'trailer', 'year', 'country', 'studio', 'set',
'genre', 'mpaa', 'setid', 'rating', 'tag', 'tagline', 'writer',
'originaltitle', 'imdbnumber', 'uniqueid']
FIELDS_TVSHOWS = FIELDS_BASE + ['sorttitle', 'mpaa', 'premiered', 'year',
'episode', 'watchedepisodes', 'votes', 'rating', 'studio', 'season',
'genre', 'cast', 'episodeguide', 'tag', 'originaltitle', 'imdbnumber']
FIELDS_SEASON = ['art', 'playcount', 'season', 'showtitle', 'episode',
'tvshowid', 'watchedepisodes', 'userrating', 'fanart', 'thumbnail']
FIELDS_EPISODES = FIELDS_FILE + ['cast', 'productioncode', 'rating', 'votes',
'episode', 'showtitle', 'tvshowid', 'season', 'firstaired', 'writer',
'originaltitle']
FIELDS_MUSICVIDEOS = FIELDS_FILE + ['genre', 'artist', 'tag', 'album', 'track',
'studio', 'year']
FIELDS_FILES = FIELDS_FILE + ['plotoutline', 'sorttitle', 'cast', 'votes',
'trailer', 'year', 'country', 'studio', 'genre', 'mpaa', 'rating',
'tagline', 'writer', 'originaltitle', 'imdbnumber', 'premiered', 'episode',
'showtitle', 'firstaired', 'watchedepisodes', 'duration', 'season']
FIELDS_SONGS = ['artist', 'displayartist', 'title', 'rating', 'fanart',
'thumbnail', 'duration', 'disc', 'playcount', 'comment', 'file', 'album',
'lastplayed', 'genre', 'musicbrainzartistid', 'track', 'dateadded']
FIELDS_ALBUMS = ['title', 'fanart', 'thumbnail', 'genre', 'displayartist',
'artist', 'musicbrainzalbumartistid', 'year', 'rating', 'artistid',
'musicbrainzalbumid', 'theme', 'description', 'type', 'style', 'playcount',
'albumlabel', 'mood', 'dateadded']
FIELDS_ARTISTS = ['born', 'formed', 'died', 'style', 'yearsactive', 'mood',
'fanart', 'thumbnail', 'musicbrainzartistid', 'disbanded', 'description',
'instrument']
FIELDS_RECORDINGS = ['art', 'channel', 'directory', 'endtime', 'file', 'genre',
'icon', 'playcount', 'plot', 'plotoutline', 'resume', 'runtime',
'starttime', 'streamurl', 'title']
FIELDS_CHANNELS = ['broadcastnow', 'channeltype', 'hidden', 'locked',
'lastplayed', 'thumbnail', 'channel']
FILTER_UNWATCHED = {
'operator': 'lessthan',
'field': 'playcount',
'value': '1'
}
FILTER_WATCHED = {
'operator': 'isnot',
'field': 'playcount',
'value': '0'
}
FILTER_RATING = {
'operator': 'greaterthan',
'field': 'rating',
'value': '7'
}
FILTER_RATING_MUSIC = {
'operator': 'greaterthan',
'field': 'rating',
'value': '3'
}
FILTER_INPROGRESS = {
'operator': 'true',
'field': 'inprogress',
'value': ''
}
SORT_RATING = {
'method': 'rating',
'order': 'descending'
}
SORT_RANDOM = {
'method': 'random',
'order': 'descending'
}
SORT_TITLE = {
'method': 'title',
'order': 'ascending'
}
SORT_DATEADDED = {
'method': 'dateadded',
'order': 'descending'
}
SORT_LASTPLAYED = {
'method': 'lastplayed',
'order': 'descending'
}
SORT_EPISODE = {
'method': 'episode'
}

View file

@ -0,0 +1,126 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .common import KODIDB_LOCK
from .video import KodiVideoDB
from .music import KodiMusicDB
from .texture import KodiTextureDB
from .. import path_ops, utils, variables as v
LOG = getLogger('PLEX.kodi_db')
def kodiid_from_filename(path, kodi_type=None, db_type=None):
"""
Returns kodi_id if we have an item in the Kodi video or audio database with
said path. Feed with either koditype, e.v. 'movie', 'song' or the DB
you want to poll ('video' or 'music')
Returns None, <kodi_type> if not possible
"""
kodi_id = None
path = utils.try_decode(path)
# Make sure path ends in either '/' or '\'
# We CANNOT use path_ops.path.join as this can result in \ where we need /
try:
filename = path.rsplit('/', 1)[1]
path = path.rsplit('/', 1)[0] + '/'
except IndexError:
filename = path.rsplit('\\', 1)[1]
path = path.rsplit('\\', 1)[0] + '\\'
if kodi_type == v.KODI_TYPE_SONG or db_type == 'music':
with KodiMusicDB(lock=False) as kodidb:
try:
kodi_id = kodidb.song_id_from_filename(filename, path)
except TypeError:
LOG.debug('No Kodi audio db element found for path %s', path)
else:
kodi_type = v.KODI_TYPE_SONG
else:
with KodiVideoDB(lock=False) as kodidb:
try:
kodi_id, kodi_type = kodidb.video_id_from_filename(filename,
path)
except TypeError:
LOG.debug('No kodi video db element found for path %s file %s',
path, filename)
return kodi_id, kodi_type
def setup_kodi_default_entries():
"""
Makes sure that we retain the Kodi standard databases. E.g. that there
is a dummy artist with ID 1
"""
if utils.settings('enableMusic') == 'true':
with KodiMusicDB() as kodidb:
kodidb.setup_kodi_default_entries()
def reset_cached_images():
LOG.info('Resetting cached artwork')
LOG.debug('Resetting the Kodi texture DB')
with KodiTextureDB() as kodidb:
kodidb.wipe()
LOG.debug('Deleting all cached image files')
path = path_ops.translate_path('special://thumbnails/')
if path_ops.exists(path):
path_ops.rmtree(path, ignore_errors=True)
paths = ('', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f',
'Video', 'plex')
for path in paths:
new_path = path_ops.translate_path('special://thumbnails/%s' % path)
try:
path_ops.makedirs(path_ops.encode_path(new_path))
except OSError as err:
LOG.warn('Could not create thumbnail directory %s: %s',
new_path, err)
LOG.info('Done resetting cached artwork')
def wipe_dbs(music=True):
"""
Completely resets the Kodi databases 'video', 'texture' and 'music' (if
music sync is enabled)
We need to connect without sqlite WAL mode as Kodi might still be accessing
the dbs and we need to prevent that
"""
LOG.warn('Wiping Kodi databases!')
LOG.info('Wiping Kodi video database')
with KodiVideoDB() as kodidb:
kodidb.wipe()
if music:
LOG.info('Wiping Kodi music database')
with KodiMusicDB() as kodidb:
kodidb.wipe()
reset_cached_images()
setup_kodi_default_entries()
# Delete SQLITE wal files
import xbmc
# Make sure Kodi knows we wiped the databases
xbmc.executebuiltin('UpdateLibrary(video)')
if utils.settings('enableMusic') == 'true':
xbmc.executebuiltin('UpdateLibrary(music)')
def create_kodi_db_indicees():
"""
Index the "actors" because we got a TON - speed up SELECT and WHEN
"""
with KodiVideoDB() as kodidb:
kodidb.create_kodi_db_indicees()
KODIDB_FROM_PLEXTYPE = {
v.PLEX_TYPE_MOVIE: KodiVideoDB,
v.PLEX_TYPE_SHOW: KodiVideoDB,
v.PLEX_TYPE_SEASON: KodiVideoDB,
v.PLEX_TYPE_EPISODE: KodiVideoDB,
v.PLEX_TYPE_ARTIST: KodiMusicDB,
v.PLEX_TYPE_ALBUM: KodiMusicDB,
v.PLEX_TYPE_SONG: KodiMusicDB
}

View file

@ -0,0 +1,155 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from threading import Lock
from .. import db, path_ops
KODIDB_LOCK = Lock()
# Names of tables we generally leave untouched and e.g. don't wipe
UNTOUCHED_TABLES = ('version', 'versiontagscan')
class KodiDBBase(object):
"""
Kodi database methods used for all types of items
"""
def __init__(self, texture_db=False, kodiconn=None, artconn=None,
lock=True):
"""
Allows direct use with a cursor instead of context mgr
"""
self._texture_db = texture_db
self.lock = lock
self.kodiconn = kodiconn
self.cursor = self.kodiconn.cursor() if self.kodiconn else None
self.artconn = artconn
self.artcursor = self.artconn.cursor() if self.artconn else None
def __enter__(self):
if self.lock:
KODIDB_LOCK.acquire()
self.kodiconn = db.connect(self.db_kind)
self.cursor = self.kodiconn.cursor()
self.artconn = db.connect('texture') if self._texture_db \
else None
self.artcursor = self.artconn.cursor() if self._texture_db else None
return self
def __exit__(self, e_typ, e_val, trcbak):
try:
if e_typ:
# re-raise any exception
return False
self.kodiconn.commit()
if self.artconn:
self.artconn.commit()
finally:
self.kodiconn.close()
if self.artconn:
self.artconn.close()
if self.lock:
KODIDB_LOCK.release()
def art_urls(self, kodi_id, kodi_type):
return (x[0] for x in
self.cursor.execute('SELECT url FROM art WHERE media_id = ? AND media_type = ?',
(kodi_id, kodi_type)))
def artwork_generator(self, kodi_type, limit, offset):
query = 'SELECT url FROM art WHERE type == ? LIMIT ? OFFSET ?'
return (x[0] for x in
self.cursor.execute(query, (kodi_type, limit, offset)))
def add_artwork(self, artworks, kodi_id, kodi_type):
"""
Pass in an artworks dict (see PlexAPI) to set an items artwork.
"""
for kodi_art, url in artworks.iteritems():
self.add_art(url, kodi_id, kodi_type, kodi_art)
@db.catch_operationalerrors
def add_art(self, url, kodi_id, kodi_type, kodi_art):
"""
Adds or modifies the artwork of kind kodi_art (e.g. 'poster') in the
Kodi art table for item kodi_id/kodi_type. Will also cache everything
except actor portraits.
"""
self.cursor.execute('''
INSERT INTO art(media_id, media_type, type, url)
VALUES (?, ?, ?, ?)
''', (kodi_id, kodi_type, kodi_art, url))
def modify_artwork(self, artworks, kodi_id, kodi_type):
"""
Pass in an artworks dict (see PlexAPI) to set an items artwork.
"""
for kodi_art, url in artworks.iteritems():
self.modify_art(url, kodi_id, kodi_type, kodi_art)
@db.catch_operationalerrors
def modify_art(self, url, kodi_id, kodi_type, kodi_art):
"""
Adds or modifies the artwork of kind kodi_art (e.g. 'poster') in the
Kodi art table for item kodi_id/kodi_type. Will also cache everything
except actor portraits.
"""
self.cursor.execute('''
SELECT url FROM art
WHERE media_id = ? AND media_type = ? AND type = ?
LIMIT 1
''', (kodi_id, kodi_type, kodi_art,))
try:
# Update the artwork
old_url = self.cursor.fetchone()[0]
except TypeError:
# Add the artwork
self.cursor.execute('''
INSERT INTO art(media_id, media_type, type, url)
VALUES (?, ?, ?, ?)
''', (kodi_id, kodi_type, kodi_art, url))
else:
if url == old_url:
# Only cache artwork if it changed
return
self.delete_cached_artwork(old_url)
self.cursor.execute('''
UPDATE art SET url = ?
WHERE media_id = ? AND media_type = ? AND type = ?
''', (url, kodi_id, kodi_type, kodi_art))
def delete_artwork(self, kodi_id, kodi_type):
self.cursor.execute('SELECT url FROM art WHERE media_id = ? AND media_type = ?',
(kodi_id, kodi_type, ))
for row in self.cursor.fetchall():
self.delete_cached_artwork(row[0])
@db.catch_operationalerrors
def delete_cached_artwork(self, url):
try:
self.artcursor.execute("SELECT cachedurl FROM texture WHERE url = ? LIMIT 1",
(url, ))
cachedurl = self.artcursor.fetchone()[0]
except TypeError:
# Could not find cached url
pass
else:
# Delete thumbnail as well as the entry
path = path_ops.translate_path("special://thumbnails/%s"
% cachedurl)
if path_ops.exists(path):
path_ops.rmtree(path, ignore_errors=True)
self.artcursor.execute("DELETE FROM texture WHERE url = ?", (url, ))
@db.catch_operationalerrors
def wipe(self):
"""
Completely wipes the corresponding Kodi database
"""
self.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table'")
tables = [i[0] for i in self.cursor.fetchall()]
for table in UNTOUCHED_TABLES:
if table in tables:
tables.remove(table)
for table in tables:
self.cursor.execute('DELETE FROM %s' % table)

View file

@ -0,0 +1,628 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from . import common
from .. import db, variables as v, app, timing
LOG = getLogger('PLEX.kodi_db.music')
class KodiMusicDB(common.KodiDBBase):
db_kind = 'music'
@db.catch_operationalerrors
def add_path(self, path):
"""
Add the path (unicode) to the music DB, if it does not exist already.
Returns the path id
"""
# SQL won't return existing paths otherwise
path = '' if path is None else path
self.cursor.execute('SELECT idPath FROM path WHERE strPath = ?',
(path,))
try:
pathid = self.cursor.fetchone()[0]
except TypeError:
self.cursor.execute('INSERT INTO path(strPath, strHash) VALUES (?, ?)',
(path, '123'))
pathid = self.cursor.lastrowid
return pathid
@db.catch_operationalerrors
def setup_kodi_default_entries(self):
"""
Makes sure that we retain the Kodi standard databases. E.g. that there
is a dummy artist with ID 1
"""
self.cursor.execute('''
INSERT OR REPLACE INTO artist(
idArtist,
strArtist,
strMusicBrainzArtistID)
VALUES (?, ?, ?)
''', (1, '[Missing Tag]', 'Artist Tag Missing'))
self.cursor.execute('''
INSERT OR REPLACE INTO role(
idRole,
strRole)
VALUES (?, ?)
''', (1, 'Artist'))
if v.KODIVERSION >= 18:
self.cursor.execute('DELETE FROM versiontagscan')
self.cursor.execute('''
INSERT INTO versiontagscan(
idVersion,
iNeedsScan,
lastscanned)
VALUES (?, ?, ?)
''', (v.DB_MUSIC_VERSION,
0,
timing.kodi_now()))
@db.catch_operationalerrors
def update_path(self, path, kodi_pathid):
self.cursor.execute('''
UPDATE path
SET strPath = ?, strHash = ?
WHERE idPath = ?
''', (path, '123', kodi_pathid))
def song_id_from_filename(self, filename, path):
"""
Returns the Kodi song_id from the Kodi music database or None if not
found OR something went wrong.
"""
self.cursor.execute('SELECT idPath FROM path WHERE strPath = ?',
(path, ))
path_ids = self.cursor.fetchall()
if len(path_ids) != 1:
LOG.debug('Found wrong number of path ids: %s for path %s, abort',
path_ids, path)
return
self.cursor.execute('SELECT idSong FROM song WHERE strFileName = ? AND idPath = ?',
(filename, path_ids[0][0]))
song_ids = self.cursor.fetchall()
if len(song_ids) != 1:
LOG.info('Found wrong number of songs %s, abort', song_ids)
return
return song_ids[0][0]
@db.catch_operationalerrors
def delete_song_from_song_artist(self, song_id):
"""
Deletes son from song_artist table and possibly orphaned roles
"""
self.cursor.execute('''
SELECT idArtist, idRole FROM song_artist
WHERE idSong = ? LIMIT 1
''', (song_id, ))
artist = self.cursor.fetchone()
if not artist:
# No entry to begin with
return
# Delete the entry
self.cursor.execute('DELETE FROM song_artist WHERE idSong = ?',
(song_id, ))
@db.catch_operationalerrors
def delete_song_from_song_genre(self, song_id):
"""
Deletes the one entry with id song_id from the song_genre table.
Will also delete orphaned genres from genre table
"""
self.cursor.execute('SELECT idGenre FROM song_genre WHERE idSong = ?',
(song_id, ))
genres = self.cursor.fetchall()
self.cursor.execute('DELETE FROM song_genre WHERE idSong = ?',
(song_id, ))
# Check for orphaned genres in both song_genre and album_genre tables
for genre in genres:
self.cursor.execute('SELECT idGenre FROM song_genre WHERE idGenre = ? LIMIT 1',
(genre[0], ))
if not self.cursor.fetchone():
self.cursor.execute('SELECT idGenre FROM album_genre WHERE idGenre = ? LIMIT 1',
(genre[0], ))
if not self.cursor.fetchone():
self.delete_genre(genre[0])
@db.catch_operationalerrors
def delete_genre(self, genre_id):
"""
Dedicated method in order to catch OperationalErrors correctly
"""
self.cursor.execute('DELETE FROM genre WHERE idGenre = ?',
(genre_id, ))
@db.catch_operationalerrors
def delete_album_from_album_genre(self, album_id):
"""
Deletes the one entry with id album_id from the album_genre table.
Will also delete orphaned genres from genre table
"""
self.cursor.execute('SELECT idGenre FROM album_genre WHERE idAlbum = ?',
(album_id, ))
genres = self.cursor.fetchall()
self.cursor.execute('DELETE FROM album_genre WHERE idAlbum = ?',
(album_id, ))
# Check for orphaned genres in both album_genre and song_genre tables
for genre in genres:
self.cursor.execute('SELECT idGenre FROM album_genre WHERE idGenre = ? LIMIT 1',
(genre[0], ))
if not self.cursor.fetchone():
self.cursor.execute('SELECT idGenre FROM song_genre WHERE idGenre = ? LIMIT 1',
(genre[0], ))
if not self.cursor.fetchone():
self.delete_genre(genre[0])
def new_album_id(self):
self.cursor.execute('SELECT COALESCE(MAX(idAlbum), 0) FROM album')
return self.cursor.fetchone()[0] + 1
@db.catch_operationalerrors
def add_album_17(self, *args):
"""
strReleaseType: 'album' or 'single'
"""
if app.SYNC.artwork:
self.cursor.execute('''
INSERT INTO album(
idAlbum,
strAlbum,
strMusicBrainzAlbumID,
strArtists,
strGenres,
iYear,
bCompilation,
strReview,
strImage,
strLabel,
iUserrating,
lastScraped,
strReleaseType)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
else:
args = list(args)
del args[8]
self.cursor.execute('''
INSERT INTO album(
idAlbum,
strAlbum,
strMusicBrainzAlbumID,
strArtists,
strGenres,
iYear,
bCompilation,
strReview,
strLabel,
iUserrating,
lastScraped,
strReleaseType)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
@db.catch_operationalerrors
def update_album_17(self, *args):
if app.SYNC.artwork:
self.cursor.execute('''
UPDATE album
SET strAlbum = ?,
strMusicBrainzAlbumID = ?,
strArtists = ?,
strGenres = ?,
iYear = ?,
bCompilation = ?,
strReview = ?,
strImage = ?,
strLabel = ?,
iUserrating = ?,
lastScraped = ?,
strReleaseType = ?
WHERE idAlbum = ?
''', (args))
else:
args = list(args)
del args[7]
self.cursor.execute('''
UPDATE album
SET strAlbum = ?,
strMusicBrainzAlbumID = ?,
strArtists = ?,
strGenres = ?,
iYear = ?,
bCompilation = ?,
strReview = ?,
strLabel = ?,
iUserrating = ?,
lastScraped = ?,
strReleaseType = ?
WHERE idAlbum = ?
''', (args))
@db.catch_operationalerrors
def add_album(self, *args):
"""
strReleaseType: 'album' or 'single'
"""
if app.SYNC.artwork:
self.cursor.execute('''
INSERT INTO album(
idAlbum,
strAlbum,
strMusicBrainzAlbumID,
strArtistDisp,
strGenres,
iYear,
bCompilation,
strReview,
strImage,
strLabel,
iUserrating,
lastScraped,
strReleaseType)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
else:
args = list(args)
del args[8]
self.cursor.execute('''
INSERT INTO album(
idAlbum,
strAlbum,
strMusicBrainzAlbumID,
strArtistDisp,
strGenres,
iYear,
bCompilation,
strReview,
strLabel,
iUserrating,
lastScraped,
strReleaseType)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
@db.catch_operationalerrors
def update_album(self, *args):
if app.SYNC.artwork:
self.cursor.execute('''
UPDATE album
SET strAlbum = ?,
strMusicBrainzAlbumID = ?,
strArtistDisp = ?,
strGenres = ?,
iYear = ?,
bCompilation = ?,
strReview = ?,
strImage = ?,
strLabel = ?,
iUserrating = ?,
lastScraped = ?,
strReleaseType = ?
WHERE idAlbum = ?
''', (args))
else:
args = list(args)
del args[7]
self.cursor.execute('''
UPDATE album
SET strAlbum = ?,
strMusicBrainzAlbumID = ?,
strArtistDisp = ?,
strGenres = ?,
iYear = ?,
bCompilation = ?,
strReview = ?,
strLabel = ?,
iUserrating = ?,
lastScraped = ?,
strReleaseType = ?
WHERE idAlbum = ?
''', (args))
@db.catch_operationalerrors
def add_albumartist(self, artist_id, kodi_id, artistname):
self.cursor.execute('''
INSERT OR REPLACE INTO album_artist(
idArtist,
idAlbum,
strArtist)
VALUES (?, ?, ?)
''', (artist_id, kodi_id, artistname))
@db.catch_operationalerrors
def add_music_genres(self, kodiid, genres, mediatype):
"""
Adds a list of genres (list of unicode) for a certain Kodi item
"""
if mediatype == "album":
# Delete current genres for clean slate
self.cursor.execute('DELETE FROM album_genre WHERE idAlbum = ?',
(kodiid, ))
for genre in genres:
self.cursor.execute('SELECT idGenre FROM genre WHERE strGenre = ?',
(genre, ))
try:
genreid = self.cursor.fetchone()[0]
except TypeError:
# Create the genre
self.cursor.execute('INSERT INTO genre(strGenre) VALUES(?)',
(genre, ))
genreid = self.cursor.lastrowid
self.cursor.execute('''
INSERT OR REPLACE INTO album_genre(
idGenre,
idAlbum)
VALUES (?, ?)
''', (genreid, kodiid))
elif mediatype == "song":
# Delete current genres for clean slate
self.cursor.execute('DELETE FROM song_genre WHERE idSong = ?',
(kodiid, ))
for genre in genres:
self.cursor.execute('SELECT idGenre FROM genre WHERE strGenre = ?',
(genre, ))
try:
genreid = self.cursor.fetchone()[0]
except TypeError:
# Create the genre
self.cursor.execute('INSERT INTO genre(strGenre) VALUES (?)',
(genre, ))
genreid = self.cursor.lastrowid
self.cursor.execute('''
INSERT OR REPLACE INTO song_genre(
idGenre,
idSong,
iOrder)
VALUES (?, ?, ?)
''', (genreid, kodiid, 0))
def add_song_id(self):
self.cursor.execute('SELECT COALESCE(MAX(idSong),0) FROM song')
return self.cursor.fetchone()[0] + 1
@db.catch_operationalerrors
def add_song(self, *args):
self.cursor.execute('''
INSERT INTO song(
idSong,
idAlbum,
idPath,
strArtistDisp,
strGenres,
strTitle,
iTrack,
iDuration,
iYear,
strFileName,
strMusicBrainzTrackID,
iTimesPlayed,
lastplayed,
rating,
iStartOffset,
iEndOffset,
mood,
dateAdded)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
@db.catch_operationalerrors
def add_song_17(self, *args):
self.cursor.execute('''
INSERT INTO song(
idSong,
idAlbum,
idPath,
strArtists,
strGenres,
strTitle,
iTrack,
iDuration,
iYear,
strFileName,
strMusicBrainzTrackID,
iTimesPlayed,
lastplayed,
rating,
iStartOffset,
iEndOffset,
mood,
dateAdded)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
@db.catch_operationalerrors
def update_song(self, *args):
self.cursor.execute('''
UPDATE song
SET idAlbum = ?,
strArtistDisp = ?,
strGenres = ?,
strTitle = ?,
iTrack = ?,
iDuration = ?,
iYear = ?,
strFilename = ?,
iTimesPlayed = ?,
lastplayed = ?,
rating = ?,
comment = ?,
mood = ?,
dateAdded = ?
WHERE idSong = ?
''', (args))
@db.catch_operationalerrors
def set_playcount(self, *args):
self.cursor.execute('''
UPDATE song
SET iTimesPlayed = ?,
lastplayed = ?
WHERE idSong = ?
''', (args))
@db.catch_operationalerrors
def update_song_17(self, *args):
self.cursor.execute('''
UPDATE song
SET idAlbum = ?,
strArtists = ?,
strGenres = ?,
strTitle = ?,
iTrack = ?,
iDuration = ?,
iYear = ?,
strFilename = ?,
iTimesPlayed = ?,
lastplayed = ?,
rating = ?,
comment = ?,
mood = ?,
dateAdded = ?
WHERE idSong = ?
''', (args))
def path_id_from_song(self, kodi_id):
self.cursor.execute('SELECT idPath FROM song WHERE idSong = ? LIMIT 1',
(kodi_id, ))
try:
return self.cursor.fetchone()[0]
except TypeError:
pass
@db.catch_operationalerrors
def add_artist(self, name, musicbrainz):
"""
Adds a single artist's name to the db
"""
self.cursor.execute('''
SELECT idArtist, strArtist
FROM artist
WHERE strMusicBrainzArtistID = ?
''', (musicbrainz, ))
try:
result = self.cursor.fetchone()
artistid = result[0]
artistname = result[1]
except TypeError:
self.cursor.execute('SELECT idArtist FROM artist WHERE strArtist = ? COLLATE NOCASE',
(name, ))
try:
artistid = self.cursor.fetchone()[0]
except TypeError:
# Krypton has a dummy first entry idArtist: 1 strArtist:
# [Missing Tag] strMusicBrainzArtistID: Artist Tag Missing
self.cursor.execute('''
INSERT INTO artist(strArtist, strMusicBrainzArtistID)
VALUES (?, ?)
''', (name, musicbrainz))
artistid = self.cursor.lastrowid
else:
if artistname != name:
self.cursor.execute('UPDATE artist SET strArtist = ? WHERE idArtist = ?',
(name, artistid,))
return artistid
@db.catch_operationalerrors
def update_artist(self, *args):
if app.SYNC.artwork:
self.cursor.execute('''
UPDATE artist
SET strGenres = ?,
strBiography = ?,
strImage = ?,
strFanart = ?,
lastScraped = ?
WHERE idArtist = ?
''', (args))
else:
args = list(args)
del args[3], args[2]
self.cursor.execute('''
UPDATE artist
SET strGenres = ?,
strBiography = ?,
lastScraped = ?
WHERE idArtist = ?
''', (args))
@db.catch_operationalerrors
def remove_song(self, kodi_id):
self.cursor.execute('DELETE FROM song WHERE idSong = ?', (kodi_id, ))
@db.catch_operationalerrors
def remove_path(self, path_id):
self.cursor.execute('DELETE FROM path WHERE idPath = ?', (path_id, ))
@db.catch_operationalerrors
def add_song_artist(self, artist_id, song_id, artist_name):
self.cursor.execute('''
INSERT OR REPLACE INTO song_artist(
idArtist,
idSong,
idRole,
iOrder,
strArtist)
VALUES (?, ?, ?, ?, ?)
''', (artist_id, song_id, 1, 0, artist_name))
@db.catch_operationalerrors
def add_albuminfosong(self, song_id, album_id, track_no, track_title,
runtime):
"""
Kodi 17 only
"""
self.cursor.execute('''
INSERT OR REPLACE INTO albuminfosong(
idAlbumInfoSong,
idAlbumInfo,
iTrack,
strTitle,
iDuration)
VALUES (?, ?, ?, ?, ?)
''', (song_id, album_id, track_no, track_title, runtime))
@db.catch_operationalerrors
def update_userrating(self, kodi_id, kodi_type, userrating):
"""
Updates userrating for songs and albums
"""
if kodi_type == v.KODI_TYPE_SONG:
column = 'userrating'
identifier = 'idSong'
elif kodi_type == v.KODI_TYPE_ALBUM:
column = 'iUserrating'
identifier = 'idAlbum'
else:
return
self.cursor.execute('''UPDATE %s SET %s = ? WHERE ? = ?'''
% (kodi_type, column),
(userrating, identifier, kodi_id))
@db.catch_operationalerrors
def remove_albuminfosong(self, kodi_id):
"""
Kodi 17 only
"""
self.cursor.execute('DELETE FROM albuminfosong WHERE idAlbumInfoSong = ?',
(kodi_id, ))
@db.catch_operationalerrors
def remove_album(self, kodi_id):
if v.KODIVERSION < 18:
self.cursor.execute('DELETE FROM albuminfosong WHERE idAlbumInfo = ?',
(kodi_id, ))
self.cursor.execute('DELETE FROM album_artist WHERE idAlbum = ?',
(kodi_id, ))
self.cursor.execute('DELETE FROM album WHERE idAlbum = ?', (kodi_id, ))
@db.catch_operationalerrors
def remove_artist(self, kodi_id):
self.cursor.execute('DELETE FROM album_artist WHERE idArtist = ?',
(kodi_id, ))
self.cursor.execute('DELETE FROM artist WHERE idArtist = ?',
(kodi_id, ))
self.cursor.execute('DELETE FROM song_artist WHERE idArtist = ?',
(kodi_id, ))

View file

@ -0,0 +1,17 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from . import common
class KodiTextureDB(common.KodiDBBase):
db_kind = 'texture'
def url_not_yet_cached(self, url):
"""
Returns True if url has not yet been cached to the Kodi texture cache
"""
self.artcursor.execute('SELECT url FROM texture WHERE url = ? LIMIT 1',
(url, ))
return self.artcursor.fetchone() is None

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,73 +1,42 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
PKC Kodi Monitoring implementation
"""
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from json import loads
from threading import Thread
import copy
import json
import binascii
import xbmc
from xbmcgui import Window
import plexdb_functions as plexdb
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 downloadutils import DownloadUtils as DU
from kodidb_functions import kodiid_from_filename
from plexbmchelper.subscribers import LOCKER
from playback import playback_triage
from initialsetup import set_replace_paths
import playqueue as PQ
import json_rpc as js
import playlist_func as PL
import state
import variables as v
from .plex_api import API
from .plex_db import PlexDB
from .kodi_db import KodiVideoDB
from . import kodi_db
from .downloadutils import DownloadUtils as DU
from . import utils, timing, plex_functions as PF
from . import json_rpc as js, playqueue as PQ, playlist_func as PL
from . import backgroundthread, app, variables as v
from . import exceptions
###############################################################################
LOG = getLogger("PLEX." + __name__)
# settings: window-variable
WINDOW_SETTINGS = {
'plex_restricteduser': 'plex_restricteduser',
'force_transcode_pix': 'plex_force_transcode_pix'
}
# settings: state-variable (state.py)
# Need to use getattr and setattr!
STATE_SETTINGS = {
'dbSyncIndicator': 'SYNC_DIALOG',
'remapSMB': 'REMAP_PATH',
'remapSMBmovieOrg': 'remapSMBmovieOrg',
'remapSMBmovieNew': 'remapSMBmovieNew',
'remapSMBtvOrg': 'remapSMBtvOrg',
'remapSMBtvNew': 'remapSMBtvNew',
'remapSMBmusicOrg': 'remapSMBmusicOrg',
'remapSMBmusicNew': 'remapSMBmusicNew',
'remapSMBphotoOrg': 'remapSMBphotoOrg',
'remapSMBphotoNew': 'remapSMBphotoNew',
'enableMusic': 'ENABLE_MUSIC',
'forceReloadSkinOnPlaybackStop': 'FORCE_RELOAD_SKIN',
'fetch_pms_item_number': 'FETCH_PMS_ITEM_NUMBER',
'imageSyncNotifications': 'IMAGE_SYNC_NOTIFICATIONS'
}
###############################################################################
LOG = getLogger('PLEX.kodimonitor')
class KodiMonitor(xbmc.Monitor):
"""
PKC implementation of the Kodi Monitor class. Invoke only once.
"""
def __init__(self):
self.xbmcplayer = xbmc.Player()
self._already_slept = False
self._switched_to_plex_streams = True
xbmc.Monitor.__init__(self)
for playerid in state.PLAYER_STATES:
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
state.OLD_PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
for playerid in app.PLAYSTATE.player_states:
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
app.PLAYSTATE.old_player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
LOG.info("Kodi monitor started.")
def onScanStarted(self, library):
@ -87,46 +56,6 @@ class KodiMonitor(xbmc.Monitor):
Monitor the PKC settings for changes made by the user
"""
LOG.debug('PKC settings change detected')
changed = False
# Reset the window variables from the settings variables
for settings_value, window_value in WINDOW_SETTINGS.iteritems():
if window(window_value) != settings(settings_value):
changed = True
LOG.debug('PKC window settings changed: %s is now %s',
settings_value, settings(settings_value))
window(window_value, value=settings(settings_value))
# Reset the state variables in state.py
for settings_value, state_name in STATE_SETTINGS.iteritems():
new = settings(settings_value)
if new == 'true':
new = True
elif new == 'false':
new = False
if getattr(state, state_name) != new:
changed = True
LOG.debug('PKC state settings %s changed from %s to %s',
settings_value, getattr(state, state_name), new)
setattr(state, state_name, new)
if state_name == 'FETCH_PMS_ITEM_NUMBER':
LOG.info('Requesting playlist/nodes refresh')
plex_command('RUN_LIB_SCAN', 'views')
# Special cases, overwrite all internal settings
set_replace_paths()
state.BACKGROUND_SYNC_DISABLED = settings(
'enableBackgroundSync') == 'false'
state.FULL_SYNC_INTERVALL = int(settings('fullSyncInterval')) * 60
state.BACKGROUNDSYNC_SAFTYMARGIN = int(
settings('backgroundsync_saftyMargin'))
state.SYNC_THREAD_NUMBER = int(settings('syncThreadNumber'))
state.SSL_CERT_PATH = settings('sslcert') \
if settings('sslcert') != 'None' else None
# Never set through the user
# state.KODI_PLEX_TIME_OFFSET = float(settings('kodiplextimeoffset'))
if changed is True:
# Assume that the user changed the settings so that we can now find
# the path to all media files
state.STOP_SYNC = False
state.PATH_VERIFIED = False
def onNotification(self, sender, method, data):
"""
@ -137,72 +66,50 @@ class KodiMonitor(xbmc.Monitor):
LOG.debug("Method: %s Data: %s", method, data)
if method == "Player.OnPlay":
state.SUSPEND_SYNC = True
self.PlayBackStart(data)
with app.APP.lock_playqueues:
self.PlayBackStart(data)
elif method == 'Player.OnAVChange':
with app.APP.lock_playqueues:
self._on_av_change(data)
elif method == "Player.OnStop":
# Should refresh our video nodes, e.g. on deck
# xbmc.executebuiltin('ReloadSkin()')
if data.get('end'):
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)
else:
_playback_cleanup()
state.PKC_CAUSED_STOP_DONE = True
state.SUSPEND_SYNC = False
with app.APP.lock_playqueues:
_playback_cleanup(ended=data.get('end'))
elif method == 'Playlist.OnAdd':
self._playlist_onadd(data)
if 'item' in data and data['item'].get('type') == v.KODI_TYPE_SHOW:
# Hitting the "browse" button on tv show info dialog
# Hence show the tv show directly
xbmc.executebuiltin("Dialog.Close(all, true)")
js.activate_window('videos',
'videodb://tvshows/titles/%s/' % data['item']['id'])
with app.APP.lock_playqueues:
self._playlist_onadd(data)
elif method == 'Playlist.OnRemove':
self._playlist_onremove(data)
elif method == 'Playlist.OnClear':
self._playlist_onclear(data)
with app.APP.lock_playqueues:
self._playlist_onclear(data)
elif method == "VideoLibrary.OnUpdate":
# Manually marking as watched/unwatched
playcount = data.get('playcount')
item = data.get('item')
if playcount is None or item is None:
return
try:
kodiid = item['id']
item_type = item['type']
except (KeyError, TypeError):
LOG.info("Item is invalid for playstate update.")
return
# Send notification to the server.
with plexdb.Get_Plex_DB() as plexcur:
plex_dbitem = plexcur.getItem_byKodiId(kodiid, item_type)
try:
itemid = plex_dbitem[0]
except TypeError:
LOG.error("Could not find itemid in plex database for a "
"video library update")
else:
# notify the server
if playcount > 0:
scrobble(itemid, 'watched')
else:
scrobble(itemid, 'unwatched')
with app.APP.lock_playqueues:
_videolibrary_onupdate(data)
elif method == "VideoLibrary.OnRemove":
pass
elif method == "System.OnSleep":
# Connection is going to sleep
LOG.info("Marking the server as offline. SystemOnSleep activated.")
window('plex_online', value="sleep")
elif method == "System.OnWake":
# Allow network to wake up
xbmc.sleep(10000)
window('plex_online', value="false")
self.waitForAbort(10)
app.CONN.online = False
elif method == "GUI.OnScreensaverDeactivated":
if settings('dbSyncScreensaver') == "true":
xbmc.sleep(5000)
plex_command('RUN_LIB_SCAN', 'full')
if utils.settings('dbSyncScreensaver') == "true":
self.waitForAbort(5)
app.SYNC.run_lib_scan = 'full'
elif method == "System.OnQuit":
LOG.info('Kodi OnQuit detected - shutting down')
state.STOP_PKC = True
app.APP.stop_pkc = True
elif method == 'Other.plugin.video.plexkodiconnect_play_action':
self._start_next_episode(data)
@LOCKER.lockthis
def _playlist_onadd(self, data):
"""
Called if an item is added to a Kodi playlist. Example data dict:
@ -215,26 +122,7 @@ class KodiMonitor(xbmc.Monitor):
}
Will NOT be called if playback initiated by Kodi widgets
"""
if 'id' not in data['item']:
return
old = state.OLD_PLAYER_STATES[data['playlistid']]
if (not state.DIRECT_PATHS and data['position'] == 0 and
not PQ.PLAYQUEUES[data['playlistid']].items and
data['item']['type'] == old['kodi_type'] and
data['item']['id'] == old['kodi_id']):
# Hack we need for RESUMABLE items because Kodi lost the path of the
# last played item that is now being replayed (see playback.py's
# Player().play()) Also see playqueue.py _compare_playqueues()
LOG.info('Detected re-start of playback of last item')
kwargs = {
'plex_id': old['plex_id'],
'plex_type': old['plex_type'],
'path': old['file'],
'resolve': False
}
thread = Thread(target=playback_triage, kwargs=kwargs)
thread.start()
return
pass
def _playlist_onremove(self, data):
"""
@ -246,8 +134,8 @@ class KodiMonitor(xbmc.Monitor):
"""
pass
@LOCKER.lockthis
def _playlist_onclear(self, data):
@staticmethod
def _playlist_onclear(data):
"""
Called if a Kodi playlist is cleared. Example data dict:
{
@ -261,7 +149,8 @@ class KodiMonitor(xbmc.Monitor):
else:
LOG.debug('Detected PKC clear - ignoring')
def _get_ids(self, kodi_id, kodi_type, path):
@staticmethod
def _get_ids(kodi_id, kodi_type, path):
"""
Returns the tuple (plex_id, plex_type) or (None, None)
"""
@ -270,16 +159,13 @@ class KodiMonitor(xbmc.Monitor):
plex_type = None
# If using direct paths and starting playback from a widget
if not kodi_id and kodi_type and path:
kodi_id = kodiid_from_filename(path, kodi_type)
kodi_id, _ = kodi_db.kodiid_from_filename(path, kodi_type)
if kodi_id:
with plexdb.Get_Plex_DB() as plex_db:
plex_dbitem = plex_db.getItem_byKodiId(kodi_id, kodi_type)
try:
plex_id = plex_dbitem[0]
plex_type = plex_dbitem[2]
except TypeError:
# No plex id, hence item not in the library. E.g. clips
pass
with PlexDB() as plexdb:
db_item = plexdb.item_by_kodi_id(kodi_id, kodi_type)
if db_item:
plex_id = db_item['plex_id']
plex_type = db_item['plex_type']
return plex_id, plex_type
@staticmethod
@ -296,8 +182,8 @@ class KodiMonitor(xbmc.Monitor):
items.pop(0)
try:
for i, item in enumerate(items):
PL.add_item_to_PMS_playlist(playqueue, i + 1, kodi_item=item)
except PL.PlaylistError:
PL.add_item_to_plex_playqueue(playqueue, i + 1, kodi_item=item)
except exceptions.PlaylistError:
LOG.info('Could not build Plex playlist for: %s', items)
def _json_item(self, playerid):
@ -311,14 +197,29 @@ class KodiMonitor(xbmc.Monitor):
# start as Kodi updates this info very late!! Might get previous
# element otherwise
self._already_slept = True
xbmc.sleep(1000)
json_item = js.get_item(playerid)
self.waitForAbort(1)
try:
json_item = js.get_item(playerid)
except KeyError:
LOG.debug('No playing item returned by Kodi')
return None, None, None
LOG.debug('Kodi playing item properties: %s', json_item)
return (json_item.get('id'),
json_item.get('type'),
json_item.get('file'))
@LOCKER.lockthis
@staticmethod
def _start_next_episode(data):
"""
Used for the add-on Upnext to start playback of the next episode
"""
LOG.info('Upnext: Start playback of the next episode')
play_info = binascii.unhexlify(data[0])
play_info = json.loads(play_info)
app.APP.player.stop()
handle = 'RunPlugin(%s)' % play_info.get('handle')
xbmc.executebuiltin(handle.encode('utf-8'))
def PlayBackStart(self, data):
"""
Called whenever playback is started. Example data:
@ -336,23 +237,41 @@ class KodiMonitor(xbmc.Monitor):
LOG.info('Aborting playback report - item invalid for updates %s',
data)
return
kodi_id = data['item'].get('id') if 'item' in data else None
kodi_type = data['item'].get('type') if 'item' in data else None
path = data['item'].get('file') if 'item' in data else None
if playerid == -1:
# Kodi might return -1 for "last player"
# Getting the playerid is really a PITA
try:
playerid = js.get_player_ids()[0]
except IndexError:
LOG.error('Could not retreive active player - aborting')
return
# E.g. Kodi 18 doesn't tell us anything useful
if kodi_type in v.KODI_VIDEOTYPES:
playlist_type = v.KODI_TYPE_VIDEO_PLAYLIST
elif kodi_type in v.KODI_AUDIOTYPES:
playlist_type = v.KODI_TYPE_AUDIO_PLAYLIST
else:
LOG.error('Unexpected type %s, data %s', kodi_type, data)
return
playerid = js.get_playlist_id(playlist_type)
if not playerid:
LOG.error('Coud not get playerid for data %s', data)
return
playqueue = PQ.PLAYQUEUES[playerid]
info = js.get_player_props(playerid)
pos = info['position'] if info['position'] != -1 else 0
LOG.debug('Detected position %s for %s', pos, playqueue)
status = state.PLAYER_STATES[playerid]
kodi_id = data.get('id')
kodi_type = data.get('type')
path = data.get('file')
if playqueue.kodi_playlist_playback:
# Kodi will tell us the wrong position - of the playlist, not the
# playqueue, when user starts playing from a playlist :-(
pos = 0
LOG.debug('Detected playback from a Kodi playlist')
else:
pos = info['position'] if info['position'] != -1 else 0
LOG.debug('Detected position %s for %s', pos, playqueue)
status = app.PLAYSTATE.player_states[playerid]
try:
item = playqueue.items[pos]
LOG.debug('PKC playqueue item is: %s', item)
except IndexError:
# PKC playqueue not yet initialized
LOG.debug('Position %s not in PKC playqueue yet', pos)
@ -370,9 +289,24 @@ class KodiMonitor(xbmc.Monitor):
# E.g. clips set-up previously with no Kodi DB entry
if not path:
kodi_id, kodi_type, path = self._json_item(playerid)
if path == '':
LOG.debug('Detected empty path: aborting playback report')
return
if item.file != path:
# Clips will get a new path
LOG.debug('Detected different path')
initialize = True
try:
tmp_plex_id = int(utils.REGEX_PLEX_ID.findall(path)[0])
except (IndexError, TypeError):
LOG.debug('No Plex id in path, need to init playqueue')
initialize = True
else:
if tmp_plex_id == item.plex_id:
LOG.debug('Detected different path for the same id')
initialize = False
else:
LOG.debug('Different Plex id, need to init playqueue')
initialize = True
else:
initialize = False
if initialize:
@ -382,9 +316,14 @@ class KodiMonitor(xbmc.Monitor):
plex_id, plex_type = self._get_ids(kodi_id, kodi_type, path)
if not plex_id:
LOG.debug('No Plex id obtained - aborting playback report')
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
return
item = PL.init_Plex_playlist(playqueue, plex_id=plex_id)
try:
item = PL.init_plex_playqueue(playqueue, plex_id=plex_id)
except exceptions.PlaylistError:
LOG.info('Could not initialize the Plex playlist')
return
item.file = path
# Set the Plex container key (e.g. using the Plex playqueue)
container_key = None
if info['playlistid'] != -1:
@ -404,8 +343,13 @@ class KodiMonitor(xbmc.Monitor):
container_key = '/playQueues/%s' % playqueue.id
else:
container_key = '/library/metadata/%s' % plex_id
# Mechanik for Plex skip intro feature
if utils.settings('enableSkipIntro') == 'true':
status['intro_markers'] = item.api.intro_markers()
# Remember the currently playing item
app.PLAYSTATE.item = item
# Remember that this player has been active
state.ACTIVE_PLAYERS.append(playerid)
app.PLAYSTATE.active_players.add(playerid)
status.update(info)
LOG.debug('Set the Plex container_key to: %s', container_key)
status['container_key'] = container_key
@ -416,34 +360,46 @@ class KodiMonitor(xbmc.Monitor):
status['plex_type'] = plex_type
status['playmethod'] = item.playmethod
status['playcount'] = item.playcount
status['external_player'] = app.APP.player.isExternalPlayer() == 1
LOG.debug('Set the player state: %s', status)
# Workaround for the Kodi add-on Up Next
if not app.SYNC.direct_paths:
_notify_upnext(item)
self._switched_to_plex_streams = False
@thread_methods
class SpecialMonitor(Thread):
"""
Detect the resume dialog for widgets.
Could also be used to detect external players (see Emby implementation)
"""
def run(self):
LOG.info("----====# Starting Special Monitor #====----")
# "Start from beginning", "Play from beginning"
strings = (try_encode(xbmc.getLocalizedString(12021)),
try_encode(xbmc.getLocalizedString(12023)))
while not self.stopped():
if xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'):
if xbmc.getInfoLabel('Control.GetLabel(1002)') in strings:
# Remember that the item IS indeed resumable
control = int(Window(10106).getFocusId())
state.RESUME_PLAYBACK = True if control == 1001 else False
else:
# Different context menu is displayed
state.RESUME_PLAYBACK = False
xbmc.sleep(200)
LOG.info("#====---- Special Monitor Stopped ----====#")
def _on_av_change(self, data):
"""
Will be called when Kodi has a video, audio or subtitle stream. Also
happens when the stream changes.
Example data as returned by Kodi:
{'item': {'id': 5, 'type': 'movie'},
'player': {'playerid': 1, 'speed': 1}}
PICKING UP CHANGES ON SUBTITLES IS CURRENTLY BROKEN ON THE KODI SIDE!
Kodi subs will never change. Also see json_rpc.py
"""
playerid = data['player']['playerid']
if not playerid == v.KODI_VIDEO_PLAYER_ID:
# We're just messing with Kodi's videoplayer
return
item = app.PLAYSTATE.item
if item is None:
# Player might've quit
return
if not self._switched_to_plex_streams:
# We need to switch to the Plex streams ONCE upon playback start
# after onavchange has been fired
if utils.settings('audioStreamPick') == '0':
item.switch_to_plex_stream('audio')
if utils.settings('subtitleStreamPick') == '0':
item.switch_to_plex_stream('subtitle')
self._switched_to_plex_streams = True
else:
item.on_av_change(playerid)
@LOCKER.lockthis
def _playback_cleanup(ended=False):
"""
PKC cleanup after playback ends/is stopped. Pass ended=True if Kodi
@ -451,16 +407,19 @@ def _playback_cleanup(ended=False):
timing data otherwise)
"""
LOG.debug('playback_cleanup called. Active players: %s',
state.ACTIVE_PLAYERS)
app.PLAYSTATE.active_players)
if app.APP.skip_intro_dialog:
app.APP.skip_intro_dialog.close()
app.APP.skip_intro_dialog = None
# 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]
app.CONN.plex_transient_token = None
for playerid in app.PLAYSTATE.active_players:
status = app.PLAYSTATE.player_states[playerid]
# Remember the last played item later
state.OLD_PLAYER_STATES[playerid] = copy.deepcopy(status)
app.PLAYSTATE.old_player_states[playerid] = copy.deepcopy(status)
# Stop transcoding
if status['playmethod'] == 'Transcode':
if status['playmethod'] == v.PLAYBACK_METHOD_TRANSCODE:
LOG.debug('Tell the PMS to stop transcoding')
DU().downloadUrl(
'{server}/video/:/transcode/universal/stop',
@ -471,9 +430,11 @@ def _playback_cleanup(ended=False):
# started playback via PMS
_record_playstate(status, ended)
# Reset the player's status
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
# As all playback has halted, reset the players that have been active
state.ACTIVE_PLAYERS = []
app.PLAYSTATE.active_players = set()
app.PLAYSTATE.item = None
utils.delete_temporary_subtitles()
LOG.debug('Finished PKC playback cleanup')
@ -481,30 +442,44 @@ 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:
if status['plex_type'] not in v.PLEX_VIDEOTYPES:
LOG.debug('Not messing with non-video entries')
return
with PlexDB() as plexdb:
db_item = plexdb.item_by_id(status['plex_id'], status['plex_type'])
if not db_item:
# 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:
totaltime = float(timing.kodi_time_to_millis(status['totaltime'])) / 1000
if status['external_player']:
# video has either been entirely watched - or not.
# "ended" won't work, need a workaround
ended = _external_player_correct_plex_watch_count(db_item)
if ended:
progress = 0.99
time = v.IGNORE_SECONDS_AT_START + 1
else:
progress = 0.0
LOG.debug('Playback progress %s (%s of %s seconds)',
progress, time, totaltime)
time = 0.0
else:
if ended:
progress = 0.99
time = v.IGNORE_SECONDS_AT_START + 1
else:
time = float(timing.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())
last_played = timing.kodi_now()
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])
with kodi_db.KodiVideoDB() as kodidb:
playcount = kodidb.get_playcount(db_item['kodi_fileid'])
playcount = 0 if playcount is None else playcount
if time < v.IGNORE_SECONDS_AT_START:
LOG.debug('Ignoring playback less than %s seconds',
@ -518,20 +493,43 @@ def _record_playstate(status, ended):
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)
with kodi_db.KodiVideoDB() as kodidb:
kodidb.set_resume(db_item['kodi_fileid'],
time,
totaltime,
playcount,
last_played)
if 'kodi_fileid_2' in db_item and db_item['kodi_fileid_2']:
# Dirty hack for our episodes
kodidb.set_resume(db_item['kodi_fileid_2'],
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
if (app.APP.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()
task = backgroundthread.FunctionAsTask(_clean_file_table, None)
backgroundthread.BGThreader.addTasksToFront([task])
def _external_player_correct_plex_watch_count(db_item):
"""
Kodi won't safe playstate at all for external players
There's currently no way to get a resumpoint if an external player is
in use We are just checking whether we should mark video as
completely watched or completely unwatched (according to
playcountminimumtime set in playercorefactory.xml)
See https://kodi.wiki/view/External_players
"""
with kodi_db.KodiVideoDB() as kodidb:
playcount = kodidb.get_playcount(db_item['kodi_fileid'])
LOG.debug('External player detected. Playcount: %s', playcount)
PF.scrobble(db_item['plex_id'], 'watched' if playcount else 'unwatched')
return True if playcount else False
def _clean_file_table():
@ -542,16 +540,146 @@ def _clean_file_table():
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:
if app.APP.monitor.waitForAbort(2):
# PKC should exit
return
try:
with kodi_db.KodiVideoDB() as kodidb:
obsolete_file_ids = list(kodidb.obsolete_file_ids())
for file_id in obsolete_file_ids:
LOG.debug('Removing obsolete Kodi file_id %s', file_id)
kodidb.remove_file(file_id, remove_orphans=False)
except utils.OperationalError:
LOG.debug('Database was locked, unable to clean file table')
else:
LOG.debug('Done cleaning up Kodi file table')
def _next_episode(current_api):
"""
Returns the xml for the next episode after the current one
Returns None if something went wrong or there is no next episode
"""
xml = PF.show_episodes(current_api.grandparent_id())
if xml is None:
return
for counter, episode in enumerate(xml):
api = API(episode)
if api.plex_id == current_api.plex_id:
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')
else:
LOG.error('Did not find the episode with Plex id %s for show %s: %s',
current_api.plex_id, current_api.grandparent_id(),
current_api.grandparent_title())
return
try:
return API(xml[counter + 1])
except IndexError:
# Was the last episode
pass
def _complete_artwork_keys(info):
"""
Make sure that the minimum set of keys is present in the info dict
"""
for key in ('tvshow.poster',
'tvshow.fanart',
'tvshow.landscape',
'tvshow.clearart',
'tvshow.clearlogo',
'thumb'):
if key not in info['art']:
info['art'][key] = ''
def _notify_upnext(item):
"""
Signals to the Kodi add-on Upnext that there is another episode after this
one.
Needed for add-on paths in order to prevent crashes when Upnext does this
by itself
"""
if not item.plex_type == v.PLEX_TYPE_EPISODE:
return
this_api = item.api
next_api = _next_episode(this_api)
if next_api is None:
return
info = {}
for key, api in (('current_episode', this_api),
('next_episode', next_api)):
info[key] = {
'episodeid': api.plex_id,
'tvshowid': api.grandparent_id(),
'title': api.title(),
'showtitle': api.grandparent_title(),
'plot': api.plot(),
'playcount': api.viewcount(),
'season': api.season_number(),
'episode': api.index(),
'firstaired': api.year(),
'rating': api.rating(),
'art': api.artwork(kodi_id=api.kodi_id,
kodi_type=api.kodi_type,
full_artwork=True)
}
_complete_artwork_keys(info[key])
info['play_info'] = {'handle': next_api.fullpath(force_addon=True)[0]}
sender = v.ADDON_ID.encode('utf-8')
method = 'upnext_data'.encode('utf-8')
data = binascii.hexlify(json.dumps(info))
data = '\\"[\\"{0}\\"]\\"'.format(data)
xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data))
def _videolibrary_onupdate(data):
"""
A specific Kodi library item has been updated. This seems to happen if the
user marks an item as watched/unwatched or if playback of the item just
stopped
2 kinds of messages possible, e.g.
Method: VideoLibrary.OnUpdate Data: ("Reset resume position" and also
fired just after stopping playback - BEFORE OnStop fires)
{'id': 1, 'type': 'movie'}
Method: VideoLibrary.OnUpdate Data: ("Mark as watched")
{'item': {'id': 1, 'type': 'movie'}, 'playcount': 1}
"""
item = data.get('item') if 'item' in data else data
try:
kodi_id = item['id']
kodi_type = item['type']
except (KeyError, TypeError):
LOG.debug("Item is invalid for a Plex playstate update")
return
playcount = data.get('playcount')
if playcount is None:
# "Reset resume position"
# Kodi might set as watched or unwatched!
with KodiVideoDB(lock=False) as kodidb:
file_id = kodidb.file_id_from_id(kodi_id, kodi_type)
if file_id is None:
return
if kodidb.get_resume(file_id):
# We do have an existing bookmark entry - not toggling to
# either watched or unwatched on the Plex side
return
playcount = kodidb.get_playcount(file_id) or 0
if app.PLAYSTATE.item and kodi_id == app.PLAYSTATE.item.kodi_id and \
kodi_type == app.PLAYSTATE.item.kodi_type:
# Kodi updates an item immediately after playback. Hence we do NOT
# increase or decrease the viewcount
return
# Send notification to the server.
with PlexDB(lock=False) as plexdb:
db_item = plexdb.item_by_kodi_id(kodi_id, kodi_type)
if not db_item:
LOG.error("Could not find plex_id in plex database for a "
"video library update")
return
# notify the server
if playcount > 0:
PF.scrobble(db_item['plex_id'], 'watched')
else:
PF.scrobble(db_item['plex_id'], 'unwatched')

View file

@ -1 +1,9 @@
# Dummy file to make this directory a package.
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from .full_sync import start
from .websocket import store_websocket_message, process_websocket_messages, \
WEBSOCKET_MESSAGES, PLAYSTATE_SESSIONS
from .common import update_kodi_library, PLAYLIST_SYNC_ENABLED
from .fanart import FanartThread, FanartTask
from .sections import force_full_sync, delete_files, clear_window_vars

View file

@ -0,0 +1,73 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import xbmc
from .. import utils, app, variables as v
LOG = getLogger('PLEX.sync')
PLAYLIST_SYNC_ENABLED = (v.DEVICE != 'Microsoft UWP' and
utils.settings('enablePlaylistSync') == 'true')
class LibrarySyncMixin(object):
def suspend(self, block=False, timeout=None):
"""
Let's NOT suspend sync threads but immediately terminate them
"""
self.cancel()
def wait_while_suspended(self):
"""
Return immediately
"""
return self.should_cancel()
def run(self):
app.APP.register_thread(self)
LOG.debug('##===--- Starting %s ---===##', self.__class__.__name__)
try:
self._run()
except Exception as err:
LOG.error('Exception encountered: %s', err)
utils.ERROR(notify=True)
finally:
app.APP.deregister_thread(self)
LOG.debug('##===--- %s Stopped ---===##', self.__class__.__name__)
def update_kodi_library(video=True, music=True):
"""
Updates the Kodi library and thus refreshes the Kodi views and widgets
"""
if video:
if not xbmc.getCondVisibility('Window.IsMedia'):
xbmc.executebuiltin('UpdateLibrary(video)')
else:
# Prevent cursor from moving - refresh later
xbmc.executebuiltin('Container.Refresh')
app.APP.update_widgets = True
if music:
xbmc.executebuiltin('UpdateLibrary(music)')
def tag_last(iterable):
"""
Given some iterable, returns (last, item), where last is only True if you
are on the final iteration.
"""
iterator = iter(iterable)
gotone = False
try:
lookback = next(iterator)
gotone = True
while True:
cur = next(iterator)
yield False, lookback
lookback = cur
except StopIteration:
if gotone:
yield True, lookback
raise StopIteration()

View file

@ -1,99 +1,154 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from threading import Thread
from Queue import Empty
from xbmc import sleep
from utils import thread_methods, settings, language as lang, dialog
import plexdb_functions as plexdb
import itemtypes
from artwork import ArtworkSyncMessage
import variables as v
import state
###############################################################################
LOG = getLogger("PLEX." + __name__)
###############################################################################
from ..plex_api import API
from ..plex_db import PlexDB
from ..kodi_db import KodiVideoDB
from .. import backgroundthread, utils
from .. import itemtypes, plex_functions as PF, variables as v, app
@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
'DB_SCAN',
'STOP_SYNC',
'SUSPEND_SYNC'])
class ThreadedProcessFanart(Thread):
LOG = getLogger('PLEX.sync.fanart')
SUPPORTED_TYPES = (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)
SYNC_FANART = (utils.settings('FanartTV') == 'true' and
utils.settings('usePlexArtwork') == 'true')
PREFER_KODI_COLLECTION_ART = utils.settings('PreferKodiCollectionArt') == 'false'
BATCH_SIZE = 500
class FanartThread(backgroundthread.KillableThread):
"""
Threaded download of additional fanart in the background
Input:
queue Queue.Queue() object that you will need to fill with
dicts of the following form:
{
'plex_id': the Plex id as a string
'plex_type': the Plex media type, e.g. 'movie'
'refresh': True/False if True, will overwrite any 3rd party
fanart. If False, will only get missing
}
This will potentially take hours!
"""
def __init__(self, queue):
self.queue = queue
Thread.__init__(self)
def __init__(self, callback, refresh=False):
self.callback = callback
self.refresh = refresh
super(FanartThread, self).__init__()
def should_suspend(self):
return self._suspended or app.APP.is_playing_video
def run(self):
"""
Do the work
"""
LOG.debug("---===### Starting FanartSync ###===---")
stopped = self.stopped
suspended = self.suspended
queue = self.queue
counter = 0
set_zero = False
while not stopped():
# In the event the server goes offline
while suspended():
# Set in service.py
if stopped():
# Abort was requested while waiting. We should exit
LOG.info("---===### Stopped FanartSync ###===---")
return
sleep(1000)
# grabs Plex item from queue
try:
item = queue.get(block=False)
except Empty:
if not set_zero:
# Avoid saving '0' all the time
set_zero = True
settings('fanarttv_lookups', value='0')
sleep(200)
continue
set_zero = False
if isinstance(item, ArtworkSyncMessage):
if state.IMAGE_SYNC_NOTIFICATIONS:
dialog('notification',
heading=lang(29999),
message=item.message,
icon='{plex}',
sound=False)
queue.task_done()
continue
LOG.info('Starting FanartThread')
app.APP.register_fanart_thread(self)
try:
self._run()
except Exception:
utils.ERROR(notify=True)
finally:
app.APP.deregister_fanart_thread(self)
LOG.debug('Get additional fanart for Plex id %s', item['plex_id'])
with getattr(itemtypes,
v.ITEMTYPE_FROM_PLEXTYPE[item['plex_type']])() as item_type:
result = item_type.getfanart(item['plex_id'],
refresh=item['refresh'])
if result is True:
LOG.debug('Done getting fanart for Plex id %s', item['plex_id'])
with plexdb.Get_Plex_DB() as plex_db:
plex_db.set_fanart_synched(item['plex_id'])
# Update the caching state in the PKC settings. Avoid saving '0'
counter += 1
if counter > 10:
counter = 0
settings('fanarttv_lookups', value=str(queue.qsize()))
queue.task_done()
LOG.debug("---===### Stopped FanartSync ###===---")
def _loop(self):
for typus in SUPPORTED_TYPES:
offset = 0
while True:
with PlexDB() as plexdb:
# Keep DB connection open only for a short period of time!
if self.refresh:
batch = list(plexdb.every_plex_id(typus,
offset,
BATCH_SIZE))
else:
batch = list(plexdb.missing_fanart(typus,
offset,
BATCH_SIZE))
for plex_id in batch:
# Do the actual, time-consuming processing
if self.should_suspend() or self.should_cancel():
return False
process_fanart(plex_id, typus, self.refresh)
if len(batch) < BATCH_SIZE:
break
offset += BATCH_SIZE
return True
def _run(self):
finished = False
while not finished:
finished = self._loop()
if self.wait_while_suspended():
break
LOG.info('FanartThread finished: %s', finished)
self.callback(finished)
class FanartTask(backgroundthread.Task):
"""
This task will also be executed while library sync is suspended!
"""
def setup(self, plex_id, plex_type, refresh=False):
self.plex_id = plex_id
self.plex_type = plex_type
self.refresh = refresh
def run(self):
process_fanart(self.plex_id, self.plex_type, self.refresh)
def process_fanart(plex_id, plex_type, refresh=False):
"""
Will look for additional fanart for the plex_type item with plex_id.
Will check if we already got all artwork and only look if some are indeed
missing.
Will set the fanart_synced flag in the Plex DB if successful.
"""
done = False
try:
artworks = None
with PlexDB() as plexdb:
db_item = plexdb.item_by_id(plex_id,
plex_type)
if not db_item:
LOG.error('Could not get Kodi id for plex id %s', plex_id)
return
if not refresh:
with KodiVideoDB() as kodidb:
artworks = kodidb.get_art(db_item['kodi_id'],
db_item['kodi_type'])
# Check if we even need to get additional art
for key in v.ALL_KODI_ARTWORK:
if key not in artworks:
break
else:
done = True
return
xml = PF.GetPlexMetadata(plex_id)
try:
xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.warn('Could not get metadata for %s. Skipping that item '
'for now', plex_id)
return
api = API(xml[0])
if artworks is None:
artworks = api.artwork()
# Get additional missing artwork from fanart artwork sites
artworks = api.fanart_artwork(artworks)
with itemtypes.ITEMTYPE_FROM_PLEXTYPE[plex_type](None) as context:
context.set_fanart(artworks,
db_item['kodi_id'],
db_item['kodi_type'])
# Additional fanart for sets/collections
if plex_type == v.PLEX_TYPE_MOVIE:
for _, setname in api.collections():
LOG.debug('Getting artwork for movie set %s', setname)
with KodiVideoDB() as kodidb:
setid = kodidb.create_collection(setname)
external_set_artwork = api.set_artwork()
if external_set_artwork and PREFER_KODI_COLLECTION_ART:
kodi_artwork = api.artwork(kodi_id=setid,
kodi_type=v.KODI_TYPE_SET)
for art in kodi_artwork:
if art in external_set_artwork:
del external_set_artwork[art]
with itemtypes.Movie(None) as movie:
movie.kodidb.modify_artwork(external_set_artwork,
setid,
v.KODI_TYPE_SET)
done = True
finally:
if done is True:
with PlexDB() as plexdb:
plexdb.set_fanart_synced(plex_id, plex_type)

View file

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from Queue import Full
from . import common, sections
from ..plex_db import PlexDB
from .. import backgroundthread
LOG = getLogger('PLEX.sync.fill_metadata_queue')
QUEUE_TIMEOUT = 60 # seconds
class FillMetadataQueue(common.LibrarySyncMixin,
backgroundthread.KillableThread):
"""
Determines which plex_ids we need to sync and puts these ids in a separate
queue. Will use a COPIED plex.db file (plex-copy.db) in order to read much
faster without the writing thread stalling
"""
def __init__(self, repair, section_queue, get_metadata_queue,
processing_queue):
self.repair = repair
self.section_queue = section_queue
self.get_metadata_queue = get_metadata_queue
self.processing_queue = processing_queue
super(FillMetadataQueue, self).__init__()
def _process_section(self, section):
# Initialize only once to avoid loosing the last value before we're
# breaking the for loop
LOG.debug('Process section %s with %s items',
section, section.number_of_items)
count = 0
do_process_section = False
with PlexDB(lock=False, copy=True) as plexdb:
for xml in section.iterator:
if self.should_cancel():
break
plex_id = int(xml.get('ratingKey'))
checksum = int('{}{}'.format(
plex_id,
abs(int(xml.get('updatedAt',
xml.get('addedAt', '1541572987'))))))
if (not self.repair and
plexdb.checksum(plex_id, section.plex_type) == checksum):
continue
if not do_process_section:
do_process_section = True
self.processing_queue.add_section(section)
LOG.debug('Put section in processing queue: %s', section)
try:
self.get_metadata_queue.put((count, plex_id, section),
timeout=QUEUE_TIMEOUT)
except Full:
LOG.error('Putting %s in get_metadata_queue timed out - '
'aborting sync now', plex_id)
section.sync_successful = False
break
else:
count += 1
# We might have received LESS items from the PMS than anticipated.
# Ensures that our queues finish
self.processing_queue.change_section_number_of_items(section,
count)
LOG.debug('%s items to process for section %s',
section.number_of_items, section)
def _run(self):
while not self.should_cancel():
section = self.section_queue.get()
self.section_queue.task_done()
if section is None:
break
self._process_section(section)
# Signal the download metadata threads to stop with a sentinel
self.get_metadata_queue.put(None)
# Sentinel for the process_thread once we added everything else
self.processing_queue.add_sentinel(sections.Section())

View file

@ -0,0 +1,325 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import Queue
import xbmcgui
from .get_metadata import GetMetadataThread
from .fill_metadata_queue import FillMetadataQueue
from .process_metadata import ProcessMetadataThread
from . import common, sections
from .. import utils, timing, backgroundthread as bg, variables as v, app
from .. import plex_functions as PF, itemtypes, path_ops
if common.PLAYLIST_SYNC_ENABLED:
from .. import playlists
LOG = getLogger('PLEX.sync.full_sync')
DELETION_BATCH_SIZE = 250
PLAYSTATE_BATCH_SIZE = 5000
# Max. number of plex_ids held in memory for later processing
BACKLOG_QUEUE_SIZE = 10000
# Max number of xmls held in memory
XML_QUEUE_SIZE = 500
# Safety margin to filter PMS items - how many seconds to look into the past?
UPDATED_AT_SAFETY = 60 * 5
LAST_VIEWED_AT_SAFETY = 60 * 5
class FullSync(common.LibrarySyncMixin, bg.KillableThread):
def __init__(self, repair, callback, show_dialog):
"""
repair=True: force sync EVERY item
"""
self.successful = True
self.repair = repair
self.callback = callback
# For progress dialog
self.show_dialog = show_dialog
self.show_dialog_userdata = utils.settings('playstate_sync_indicator') == 'true'
if self.show_dialog:
self.dialog = xbmcgui.DialogProgressBG()
self.dialog.create(utils.lang(39714))
else:
self.dialog = None
self.current_time = timing.plex_now()
self.last_section = sections.Section()
self.install_sync_done = utils.settings('SyncInstallRunDone') == 'true'
super(FullSync, self).__init__()
def update_progressbar(self, section, title, current):
if not self.dialog:
return
current += 1
try:
progress = int(float(current) / float(section.number_of_items) * 100.0)
except ZeroDivisionError:
progress = 0
self.dialog.update(progress,
'%s (%s)' % (section.name, section.section_type_text),
'%s %s/%s'
% (title, current, section.number_of_items))
if app.APP.is_playing_video:
self.dialog.close()
self.dialog = None
@staticmethod
def copy_plex_db():
"""
Takes the current plex.db file and copies it to plex-copy.db
This will allow us to have "concurrent" connections during adding/
updating items, increasing sync speed tremendously.
Using the same DB with e.g. WAL mode did not really work out...
"""
path_ops.copyfile(v.DB_PLEX_PATH, v.DB_PLEX_COPY_PATH)
@utils.log_time
def process_new_and_changed_items(self, section_queue, processing_queue):
LOG.debug('Start working')
get_metadata_queue = Queue.Queue(maxsize=BACKLOG_QUEUE_SIZE)
scanner_thread = FillMetadataQueue(self.repair,
section_queue,
get_metadata_queue,
processing_queue)
scanner_thread.start()
metadata_threads = [
GetMetadataThread(get_metadata_queue, processing_queue)
for _ in range(int(utils.settings('syncThreadNumber')))
]
for t in metadata_threads:
t.start()
process_thread = ProcessMetadataThread(self.current_time,
processing_queue,
self.update_progressbar)
process_thread.start()
LOG.debug('Waiting for scanner thread to finish up')
scanner_thread.join()
LOG.debug('Waiting for metadata download threads to finish up')
for t in metadata_threads:
t.join()
LOG.debug('Download metadata threads finished')
process_thread.join()
self.successful = process_thread.successful
LOG.debug('threads finished work. successful: %s', self.successful)
@utils.log_time
def processing_loop_playstates(self, section_queue):
while not self.should_cancel():
section = section_queue.get()
section_queue.task_done()
if section is None:
break
self.playstate_per_section(section)
def playstate_per_section(self, section):
LOG.debug('Processing %s playstates for library section %s',
section.number_of_items, section)
try:
with section.context(self.current_time) as context:
for xml in section.iterator:
section.count += 1
if not context.update_userdata(xml, section.plex_type):
# Somehow did not sync this item yet
context.add_update(xml,
section_name=section.name,
section_id=section.section_id)
context.plexdb.update_last_sync(int(xml.attrib['ratingKey']),
section.plex_type,
self.current_time)
self.update_progressbar(section, '', section.count - 1)
if section.count % PLAYSTATE_BATCH_SIZE == 0:
context.commit()
except RuntimeError:
LOG.error('Could not entirely process section %s', section)
self.successful = False
def threaded_get_generators(self, kinds, section_queue, items):
"""
Getting iterators is costly, so let's do it in a dedicated thread
"""
LOG.debug('Start threaded_get_generators')
try:
for kind in kinds:
for section in (x for x in app.SYNC.sections
if x.section_type == kind[1]):
if self.should_cancel():
LOG.debug('Need to exit now')
return
if not section.sync_to_kodi:
LOG.info('User chose to not sync section %s', section)
continue
section = sections.get_sync_section(section,
plex_type=kind[0])
timestamp = section.last_sync - UPDATED_AT_SAFETY \
if section.last_sync else None
if items == 'all':
updated_at = None
last_viewed_at = None
elif items == 'watched':
if not timestamp:
# No need to sync playstate updates since section
# has not yet been synched
continue
else:
updated_at = None
last_viewed_at = timestamp
elif items == 'updated':
updated_at = timestamp
last_viewed_at = None
try:
section.iterator = PF.get_section_iterator(
section.section_id,
plex_type=section.plex_type,
updated_at=updated_at,
last_viewed_at=last_viewed_at)
except RuntimeError:
LOG.error('Sync at least partially unsuccessful!')
LOG.error('Error getting section iterator %s', section)
else:
section.number_of_items = section.iterator.total
if section.number_of_items > 0:
section_queue.put(section)
LOG.debug('Put section in queue with %s items: %s',
section.number_of_items, section)
except Exception:
utils.ERROR(notify=True)
finally:
# Sentinel for the section queue
section_queue.put(None)
LOG.debug('Exiting threaded_get_generators')
def full_library_sync(self):
section_queue = Queue.Queue()
processing_queue = bg.ProcessingQueue(maxsize=XML_QUEUE_SIZE)
kinds = [
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_MOVIE),
(v.PLEX_TYPE_SHOW, v.PLEX_TYPE_SHOW),
(v.PLEX_TYPE_SEASON, v.PLEX_TYPE_SHOW),
(v.PLEX_TYPE_EPISODE, v.PLEX_TYPE_SHOW)
]
if app.SYNC.enable_music:
kinds.extend([
(v.PLEX_TYPE_ARTIST, v.PLEX_TYPE_ARTIST),
(v.PLEX_TYPE_ALBUM, v.PLEX_TYPE_ARTIST),
])
# ADD NEW ITEMS
# We need to enforce syncing e.g. show before season before episode
bg.FunctionAsTask(self.threaded_get_generators,
None,
kinds,
section_queue,
items='all' if self.repair else 'updated').start()
# Do the heavy lifting
self.process_new_and_changed_items(section_queue, processing_queue)
common.update_kodi_library(video=True, music=True)
if self.should_cancel() or not self.successful:
return
# In order to not delete all your songs again for playstate synch
if app.SYNC.enable_music:
kinds.extend([
(v.PLEX_TYPE_SONG, v.PLEX_TYPE_ARTIST),
])
# Update playstate progress since last sync - especially useful for
# users of very large libraries since this step is very fast
# These playstates will be synched twice
LOG.debug('Start synching playstate for last watched items')
bg.FunctionAsTask(self.threaded_get_generators,
None,
kinds,
section_queue,
items='watched').start()
self.processing_loop_playstates(section_queue)
if self.should_cancel() or not self.successful:
return
# Sync Plex playlists to Kodi and vice-versa
if common.PLAYLIST_SYNC_ENABLED:
LOG.debug('Start playlist sync')
if self.show_dialog:
if self.dialog:
self.dialog.close()
self.dialog = xbmcgui.DialogProgressBG()
# "Synching playlists"
self.dialog.create(utils.lang(39715))
if not playlists.full_sync() or self.should_cancel():
return
# SYNC PLAYSTATE of ALL items (otherwise we won't pick up on items that
# were set to unwatched or changed user ratings). Also mark all items on
# the PMS to be able to delete the ones still in Kodi
LOG.debug('Start synching playstate and userdata for every item')
# Make sure we're not showing an item's title in the sync dialog
if not self.show_dialog_userdata and self.dialog:
# Close the progress indicator dialog
self.dialog.close()
self.dialog = None
bg.FunctionAsTask(self.threaded_get_generators,
None,
kinds,
section_queue,
items='all').start()
self.processing_loop_playstates(section_queue)
if self.should_cancel() or not self.successful:
return
# Delete movies that are not on Plex anymore
LOG.debug('Looking for items to delete')
kinds = [
(v.PLEX_TYPE_MOVIE, itemtypes.Movie),
(v.PLEX_TYPE_SHOW, itemtypes.Show),
(v.PLEX_TYPE_SEASON, itemtypes.Season),
(v.PLEX_TYPE_EPISODE, itemtypes.Episode)
]
if app.SYNC.enable_music:
kinds.extend([
(v.PLEX_TYPE_ARTIST, itemtypes.Artist),
(v.PLEX_TYPE_ALBUM, itemtypes.Album),
(v.PLEX_TYPE_SONG, itemtypes.Song)
])
for plex_type, context in kinds:
# Delete movies that are not on Plex anymore
while True:
with context(self.current_time) as ctx:
plex_ids = list(
ctx.plexdb.plex_id_by_last_sync(plex_type,
self.current_time,
DELETION_BATCH_SIZE))
for plex_id in plex_ids:
if self.should_cancel():
return
ctx.remove(plex_id, plex_type)
if len(plex_ids) < DELETION_BATCH_SIZE:
break
LOG.debug('Done looking for items to delete')
@utils.log_time
def _run(self):
try:
# Get latest Plex libraries and build playlist and video node files
if self.should_cancel() or not sections.sync_from_pms(self):
return
self.copy_plex_db()
self.full_library_sync()
finally:
common.update_kodi_library(video=True, music=True)
if self.dialog:
self.dialog.close()
if not self.successful and not self.should_cancel():
# "ERROR in library sync"
utils.dialog('notification',
heading='{plex}',
message=utils.lang(39410),
icon='{error}')
self.callback(self.successful)
def start(show_dialog, repair=False, callback=None):
# Call run() and NOT start in order to not spawn another thread
FullSync(repair, callback, show_dialog).run()

View file

@ -1,122 +1,123 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from threading import Thread
from Queue import Empty
from xbmc import sleep
from . import common
from ..plex_api import API
from .. import backgroundthread, plex_functions as PF, utils, variables as v
from utils import thread_methods, window
from PlexFunctions import GetPlexMetadata, GetAllPlexChildren
import sync_info
###############################################################################
LOG = getLogger("PLEX." + __name__)
###############################################################################
LOG = getLogger('PLEX.sync.get_metadata')
LOCK = backgroundthread.threading.Lock()
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
'STOP_SYNC',
'SUSPEND_SYNC'])
class ThreadedGetMetadata(Thread):
class GetMetadataThread(common.LibrarySyncMixin,
backgroundthread.KillableThread):
"""
Threaded download of Plex XML metadata for a certain library item.
Fills the out_queue with the downloaded etree XML objects
Input:
queue Queue.Queue() object that you'll need to fill up
with plex_ids
out_queue Queue() object where this thread will store
the downloaded metadata XMLs as etree objects
Fills the queue with the downloaded etree XML objects
"""
def __init__(self, queue, out_queue):
self.queue = queue
self.out_queue = out_queue
Thread.__init__(self)
def __init__(self, get_metadata_queue, processing_queue):
self.get_metadata_queue = get_metadata_queue
self.processing_queue = processing_queue
super(GetMetadataThread, self).__init__()
def terminate_now(self):
"""
Needed to terminate this thread, because there might be items left in
the queue which could cause other threads to hang
"""
while not self.queue.empty():
# Still try because remaining item might have been taken
try:
self.queue.get(block=False)
except Empty:
sleep(10)
continue
else:
self.queue.task_done()
if self.stopped():
# Shutdown from outside requested; purge out_queue as well
while not self.out_queue.empty():
# Still try because remaining item might have been taken
try:
self.out_queue.get(block=False)
except Empty:
sleep(10)
def _collections(self, item):
api = API(item['xml'][0])
collection_match = item['section'].collection_match
collection_xmls = item['section'].collection_xmls
if collection_match is None:
collection_match = PF.collections(api.library_section_id())
if collection_match is None:
LOG.error('Could not download collections')
return
# Extract what we need to know
collection_match = \
[(utils.cast(int, x.get('index')),
utils.cast(int, x.get('ratingKey'))) for x in collection_match]
item['children'] = {}
for plex_set_id, set_name in api.collections():
if self.should_cancel():
return
if plex_set_id not in collection_xmls:
# Get Plex metadata for collections - a pain
for index, collection_plex_id in collection_match:
if index == plex_set_id:
collection_xml = PF.GetPlexMetadata(collection_plex_id)
try:
collection_xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error('Could not get collection %s %s',
collection_plex_id, set_name)
continue
collection_xmls[plex_set_id] = collection_xml
break
else:
LOG.error('Did not find Plex collection %s %s',
plex_set_id, set_name)
continue
else:
self.out_queue.task_done()
item['children'][plex_set_id] = collection_xmls[plex_set_id]
def run(self):
"""
Do the work
"""
LOG.debug('Starting get metadata thread')
# cache local variables because it's faster
queue = self.queue
out_queue = self.out_queue
stopped = self.stopped
while stopped() is False:
# grabs Plex item from queue
def _process_abort(self, count, section):
# Make sure other threads will also receive sentinel
self.get_metadata_queue.put(None)
if count is not None:
self._process_skipped_item(count, section)
def _process_skipped_item(self, count, section):
section.sync_successful = False
# Add a "dummy" item so we're not skipping a beat
self.processing_queue.put((count, {'section': section, 'xml': None}))
def _run(self):
while True:
item = self.get_metadata_queue.get()
try:
item = queue.get(block=False)
# Empty queue
except Empty:
sleep(20)
continue
# Download Metadata
xml = GetPlexMetadata(item['plex_id'])
if xml is None:
# Did not receive a valid XML - skip that item for now
LOG.error("Could not get metadata for %s. Skipping that item "
"for now", item['plex_id'])
# Increase BOTH counters - since metadata won't be processed
with sync_info.LOCK:
sync_info.GET_METADATA_COUNT += 1
sync_info.PROCESS_METADATA_COUNT += 1
queue.task_done()
continue
elif xml == 401:
LOG.error('HTTP 401 returned by PMS. Too much strain? '
'Cancelling sync for now')
window('plex_scancrashed', value='401')
# Kill remaining items in queue (for main thread to cont.)
queue.task_done()
break
item['xml'] = xml
if item.get('get_children') is True:
children_xml = GetAllPlexChildren(item['plex_id'])
try:
children_xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error('Could not get children for Plex id %s',
item['plex_id'])
item['children'] = []
else:
item['children'] = children_xml
# place item into out queue
out_queue.put(item)
# Keep track of where we are at
with sync_info.LOCK:
sync_info.GET_METADATA_COUNT += 1
# signals to queue job is done
queue.task_done()
# Empty queue in case PKC was shut down (main thread hangs otherwise)
self.terminate_now()
LOG.debug('Get metadata thread terminated')
if item is None or self.should_cancel():
self._process_abort(item[0] if item else None,
item[2] if item else None)
break
count, plex_id, section = item
item = {
'xml': PF.GetPlexMetadata(plex_id), # This will block
'children': None,
'section': section
}
if item['xml'] is None:
# Did not receive a valid XML - skip that item for now
LOG.error("Could not get metadata for %s. Skipping item "
"for now", plex_id)
self._process_skipped_item(count, section)
continue
elif item['xml'] == 401:
LOG.error('HTTP 401 returned by PMS. Too much strain? '
'Cancelling sync for now')
utils.window('plex_scancrashed', value='401')
self._process_abort(count, section)
break
if section.plex_type == v.PLEX_TYPE_MOVIE:
# Check for collections/sets
collections = False
for child in item['xml'][0]:
if child.tag == 'Collection':
collections = True
break
if collections:
with LOCK:
self._collections(item)
if section.get_children:
if self.should_cancel():
self._process_abort(count, section)
break
children_xml = PF.GetAllPlexChildren(plex_id) # Will block
try:
children_xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error('Could not get children for Plex id %s',
plex_id)
self._process_skipped_item(count, section)
continue
else:
item['children'] = children_xml
self.processing_queue.put((count, item))
finally:
self.get_metadata_queue.task_done()

View file

@ -0,0 +1,412 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
import urllib
import copy
from ..utils import etree
from .. import variables as v, utils
ICON_PATH = 'special://home/addons/plugin.video.plexkodiconnect/icon.png'
RECOMMENDED_SCORE_LOWER_BOUND = 7
# Logic of the following nodes:
# (node_type,
# label/node name,
# args for PKC add-on callback,
# Kodi "content",
# Bool: does this node's xml even point back to PKC add-on callback?
# )
NODE_TYPES = {
v.PLEX_TYPE_MOVIE: (
('plex_ondeck',
utils.lang(39500), # "On Deck"
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}/onDeck',
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_MOVIE,
True),
('ondeck',
utils.lang(39502), # "PKC On Deck (faster)"
{},
v.CONTENT_TYPE_MOVIE,
False),
('recent',
utils.lang(30174), # "Recently Added"
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}/recentlyAdded',
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_MOVIE,
False),
('all',
'{self.name}', # We're using this section's name
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}/all',
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_MOVIE,
False),
('recommended',
utils.lang(30230), # "Recommended"
{
'mode': 'browseplex',
'key': ('/library/sections/{self.section_id}&%s'
% urllib.urlencode({'sort': 'rating:desc'})),
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_MOVIE,
False),
('genres',
utils.lang(135), # "Genres"
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}/genre',
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_MOVIE,
False),
('sets',
utils.lang(39501), # "Collections"
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}/collection',
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_MOVIE,
False),
('random',
utils.lang(30227), # "Random"
{
'mode': 'browseplex',
'key': ('/library/sections/{self.section_id}&%s'
% urllib.urlencode({'sort': 'random'})),
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_MOVIE,
False),
('lastplayed',
utils.lang(568), # "Last played"
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}/recentlyViewed',
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_MOVIE,
False),
('browse',
utils.lang(39702), # "Browse by folder"
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}/folder',
'plex_type': '{self.section_type}',
'section_id': '{self.section_id}',
'folder': True
},
v.CONTENT_TYPE_MOVIE,
True),
('more',
utils.lang(22082), # "More..."
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}',
'section_id': '{self.section_id}',
'folder': True
},
v.CONTENT_TYPE_FILE,
True),
),
###########################################################
v.PLEX_TYPE_SHOW: (
('ondeck',
utils.lang(39500), # "On Deck"
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}/onDeck',
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_EPISODE,
True),
('recent',
utils.lang(30174), # "Recently Added"
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}/recentlyAdded',
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_EPISODE,
False),
('all',
'{self.name}', # We're using this section's name
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}/all',
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_SHOW,
False),
('recommended',
utils.lang(30230), # "Recommended"
{
'mode': 'browseplex',
'key': ('/library/sections/{self.section_id}&%s'
% urllib.urlencode({'sort': 'rating:desc'})),
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_SHOW,
False),
('genres',
utils.lang(135), # "Genres"
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}/genre',
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_SHOW,
False),
('sets',
utils.lang(39501), # "Collections"
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}/collection',
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_SHOW,
True), # There are no sets/collections for shows with Kodi
('random',
utils.lang(30227), # "Random"
{
'mode': 'browseplex',
'key': ('/library/sections/{self.section_id}&%s'
% urllib.urlencode({'sort': 'random'})),
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_SHOW,
False),
('lastplayed',
utils.lang(568), # "Last played"
{
'mode': 'browseplex',
'key': ('/library/sections/{self.section_id}/recentlyViewed&%s'
% urllib.urlencode({'type': v.PLEX_TYPE_NUMBER_FROM_PLEX_TYPE[v.PLEX_TYPE_EPISODE]})),
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_EPISODE,
False),
('browse',
utils.lang(39702), # "Browse by folder"
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}/folder',
'section_id': '{self.section_id}',
'folder': True
},
v.CONTENT_TYPE_EPISODE,
True),
('more',
utils.lang(22082), # "More..."
{
'mode': 'browseplex',
'key': '/library/sections/{self.section_id}',
'section_id': '{self.section_id}',
'folder': True
},
v.CONTENT_TYPE_FILE,
True),
),
}
def node_pms(section, node_name, args):
"""
Nodes where the logic resides with the PMS - we're NOT building an
xml that filters and sorts, but point to PKC add-on path
Be sure to set args['folder'] = True if the listing is a folder and does
not contain playable elements like movies, episodes or tracks
"""
if 'folder' in args:
args = copy.deepcopy(args)
args.pop('folder')
folder = True
else:
folder = False
xml = etree.Element('node',
attrib={'order': unicode(section.order),
'type': 'folder' if folder else 'filter'})
etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml, 'path').text = section.addon_path(args)
return xml
def node_ondeck(section, node_name):
"""
For movies only - returns in-progress movies sorted by last played
"""
xml = etree.Element('node', attrib={'order': unicode(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
'operator': 'is'})
etree.SubElement(rule, 'value').text = section.name
etree.SubElement(xml, 'rule', attrib={'field': 'inprogress',
'operator': 'true'})
etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
etree.SubElement(xml,
'order',
attrib={'direction':
'descending'}).text = 'lastplayed'
return xml
def node_recent(section, node_name):
xml = etree.Element('node',
attrib={'order': unicode(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
'operator': 'is'})
etree.SubElement(rule, 'value').text = section.name
if ((section.section_type == v.PLEX_TYPE_SHOW and
utils.settings('TVShowWatched') == 'false') or
(section.section_type == v.PLEX_TYPE_MOVIE and
utils.settings('MovieShowWatched') == 'false')):
# Adds an additional rule if user deactivated the PKC setting
# "Recently Added: Also show already watched episodes"
# or
# "Recently Added: Also show already watched episodes"
rule = etree.SubElement(xml, 'rule', attrib={'field': 'playcount',
'operator': 'is'})
etree.SubElement(rule, 'value').text = '0'
etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
etree.SubElement(xml,
'order',
attrib={'direction':
'descending'}).text = 'dateadded'
return xml
def node_all(section, node_name):
xml = etree.Element('node', attrib={'order': unicode(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
'operator': 'is'})
etree.SubElement(rule, 'value').text = section.name
etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml,
'order',
attrib={'direction':
'ascending'}).text = 'sorttitle'
return xml
def node_recommended(section, node_name):
xml = etree.Element('node', attrib={'order': unicode(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
'operator': 'is'})
etree.SubElement(rule, 'value').text = section.name
# rule = etree.SubElement(xml, 'rule', attrib={'field': 'rating',
# 'operator': 'greaterthan'})
# etree.SubElement(rule, 'value').text = unicode(RECOMMENDED_SCORE_LOWER_BOUND)
etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
etree.SubElement(xml,
'order',
attrib={'direction':
'descending'}).text = 'rating'
return xml
def node_genres(section, node_name):
xml = etree.Element('node', attrib={'order': unicode(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
'operator': 'is'})
etree.SubElement(rule, 'value').text = section.name
etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml,
'order',
attrib={'direction':
'ascending'}).text = 'sorttitle'
etree.SubElement(xml, 'group').text = 'genres'
return xml
def node_sets(section, node_name):
xml = etree.Element('node', attrib={'order': unicode(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
'operator': 'is'})
etree.SubElement(rule, 'value').text = section.name
# "Collections"
etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml,
'order',
attrib={'direction':
'ascending'}).text = 'sorttitle'
etree.SubElement(xml, 'group').text = 'sets'
return xml
def node_random(section, node_name):
xml = etree.Element('node', attrib={'order': unicode(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
'operator': 'is'})
etree.SubElement(rule, 'value').text = section.name
etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
etree.SubElement(xml,
'order',
attrib={'direction':
'ascending'}).text = 'random'
return xml
def node_lastplayed(section, node_name):
xml = etree.Element('node', attrib={'order': unicode(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
'operator': 'is'})
etree.SubElement(rule, 'value').text = section.name
rule = etree.SubElement(xml, 'rule', attrib={'field': 'playcount',
'operator': 'greaterthan'})
etree.SubElement(rule, 'value').text = '0'
etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
etree.SubElement(xml,
'order',
attrib={'direction':
'descending'}).text = 'lastplayed'
return xml

View file

@ -1,88 +1,92 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from threading import Thread
from Queue import Empty
from xbmc import sleep
from . import common, sections
from ..plex_db import PlexDB
from .. import backgroundthread, app
from utils import thread_methods
import itemtypes
import sync_info
LOG = getLogger('PLEX.sync.process_metadata')
###############################################################################
LOG = getLogger("PLEX." + __name__)
###############################################################################
COMMIT_TO_DB_EVERY_X_ITEMS = 500
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
'STOP_SYNC',
'SUSPEND_SYNC'])
class ThreadedProcessMetadata(Thread):
class ProcessMetadataThread(common.LibrarySyncMixin,
backgroundthread.KillableThread):
"""
Not yet implemented for more than 1 thread - if ever. Only to be called by
ONE thread!
Processes the XML metadata in the queue
Input:
queue: Queue.Queue() object that you'll need to fill up with
the downloaded XML eTree objects
item_class: as used to call functions in itemtypes.py e.g. 'Movies' =>
itemtypes.Movies()
Invoke once in order to process the received PMS metadata xmls
"""
def __init__(self, queue, item_class):
self.queue = queue
self.item_class = item_class
Thread.__init__(self)
def __init__(self, current_time, processing_queue, update_progressbar):
self.current_time = current_time
self.processing_queue = processing_queue
self.update_progressbar = update_progressbar
self.last_section = sections.Section()
self.successful = True
super(ProcessMetadataThread, self).__init__()
def terminate_now(self):
"""
Needed to terminate this thread, because there might be items left in
the queue which could cause other threads to hang
"""
while not self.queue.empty():
# Still try because remaining item might have been taken
try:
self.queue.get(block=False)
except Empty:
sleep(10)
continue
else:
self.queue.task_done()
def start_section(self, section):
if section != self.last_section:
if self.last_section:
self.finish_last_section()
LOG.debug('Start or continue processing section %s', section)
self.last_section = section
# Warn the user for this new section if we cannot access a file
app.SYNC.path_verified = False
else:
LOG.debug('Resume processing section %s', section)
def run(self):
"""
Do the work
"""
LOG.debug('Processing thread started')
# Constructs the method name, e.g. itemtypes.Movies
item_fct = getattr(itemtypes, self.item_class)
# cache local variables because it's faster
queue = self.queue
stopped = self.stopped
with item_fct() as item_class:
while stopped() is False:
# grabs item from queue
try:
item = queue.get(block=False)
except Empty:
sleep(20)
continue
# Do the work
item_method = getattr(item_class, item['method'])
if item.get('children'):
item_method(item['xml'][0],
viewtag=item['view_name'],
viewid=item['view_id'],
children=item['children'])
else:
item_method(item['xml'][0],
viewtag=item['view_name'],
viewid=item['view_id'])
# Keep track of where we are at
with sync_info.LOCK:
sync_info.PROCESS_METADATA_COUNT += 1
sync_info.PROCESSING_VIEW_NAME = item['title']
queue.task_done()
self.terminate_now()
LOG.debug('Processing thread terminated')
def finish_last_section(self):
if (not self.should_cancel() and self.last_section and
self.last_section.sync_successful):
# Check for should_cancel() because we cannot be sure that we
# processed every item of the section
with PlexDB() as plexdb:
# Set the new time mark for the next delta sync
plexdb.update_section_last_sync(self.last_section.section_id,
self.current_time)
LOG.info('Finished processing section successfully: %s',
self.last_section)
elif self.last_section and not self.last_section.sync_successful:
LOG.warn('Sync not successful for section %s', self.last_section)
self.successful = False
def _get(self):
item = {'xml': None}
while item and item['xml'] is None:
item = self.processing_queue.get()
self.processing_queue.task_done()
return item
def _run(self):
# There are 2 sentinels: None for aborting/ending this thread, the dict
# {'section': section, 'xml': None} for skipped/invalid items
item = self._get()
if item:
section = item['section']
processed = 0
self.start_section(section)
while not self.should_cancel():
if item is None:
break
elif item['section'] != section:
# We received an entirely new section
self.start_section(item['section'])
section = item['section']
with section.context(self.current_time) as context:
while not self.should_cancel():
if item is None or item['section'] != section:
break
self.update_progressbar(section,
item['xml'][0].get('title'),
section.count)
context.add_update(item['xml'][0],
section_name=section.name,
section_id=section.section_id,
children=item['children'])
processed += 1
section.count += 1
if processed == COMMIT_TO_DB_EVERY_X_ITEMS:
processed = 0
context.commit()
item = self._get()
self.finish_last_section()

View file

@ -0,0 +1,745 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import copy
from . import nodes
from ..plex_db import PlexDB
from ..plex_api import API
from .. import kodi_db
from .. import itemtypes, path_ops
from .. import plex_functions as PF, music, utils, variables as v, app
from ..utils import etree
LOG = getLogger('PLEX.sync.sections')
BATCH_SIZE = 500
# Need a way to interrupt our synching process
SHOULD_CANCEL = None
LIBRARY_PATH = path_ops.translate_path('special://profile/library/video/')
# The video library might not yet exist for this user - create it
if not path_ops.exists(LIBRARY_PATH):
path_ops.copy_tree(
src=path_ops.translate_path('special://xbmc/system/library/video'),
dst=LIBRARY_PATH,
preserve_mode=0) # dont copy permission bits so we have write access!
PLAYLISTS_PATH = path_ops.translate_path("special://profile/playlists/video/")
if not path_ops.exists(PLAYLISTS_PATH):
path_ops.makedirs(PLAYLISTS_PATH)
# Windows variables we set for each node
WINDOW_ARGS = ('index', 'title', 'id', 'path', 'type', 'content', 'artwork')
class Section(object):
"""
Setting the attribute section_type will automatically set content and
sync_to_kodi
"""
def __init__(self, index=None, xml_element=None, section_db_element=None):
# Unique Plex id of this Plex library section
self._section_id = None # int
# Building block for window variable
self._node = None # unicode
# Index of this section (as section_id might not be subsequent)
# This follows 1:1 the sequence in with the PMS returns the sections
self._index = None # Codacy-bug
self.index = index # int
# This section's name for the user to display
self.name = None # unicode
# Library type section (NOT the same as the KODI_TYPE_...)
# E.g. 'movies', 'tvshows', 'episodes'
self.content = None # unicode
# Setting the section_type WILL re_set sync_to_kodi!
self._section_type = None # unicode
# E.g. "season" or "movie" (translated)
self.section_type_text = None
# Do we sync all items of this section to the Kodi DB?
# This will be set with section_type!!
self.sync_to_kodi = None # bool
# For sections to be synched, the section name will be recorded as a
# tag. This is the corresponding id for this tag
self.kodi_tagid = None # int
# When was this section last successfully/completely synched to the
# Kodi database?
self.last_sync = None # int
# Path to the Kodi userdata library FOLDER for this section
self._path = None # unicode
# Path to the smart playlist for this section
self._playlist_path = None
# "Poster" for this section
self.icon = None # unicode
# Background image for this section
self.artwork = None
# Thumbnail for this section, similar for each section type
self.thumb = None
# Order number in which xmls will be listed inside Kodei
self.order = None
# Original PMS xml for this section, including children
self.xml = None
# A section_type encompasses possible several plex_types! E.g. shows
# contain shows, seasons, episodes
self._plex_type = None
if xml_element is not None:
self.from_xml(xml_element)
elif section_db_element:
self.from_db_element(section_db_element)
def __repr__(self):
return ("{{"
"'index': {self.index}, "
"'name': '{self.name}', "
"'section_id': {self.section_id}, "
"'section_type': '{self.section_type}', "
"'plex_type': '{self.plex_type}', "
"'sync_to_kodi': {self.sync_to_kodi}, "
"'last_sync': {self.last_sync}"
"}}").format(self=self).encode('utf-8')
__str__ = __repr__
def __nonzero__(self):
return (self.section_id is not None and
self.name is not None and
self.section_type is not None)
def __eq__(self, section):
"""
Sections compare equal if their section_id, name and plex_type (first
prio) OR section_type (if there is no plex_type is set) compare equal
"""
if not isinstance(section, Section):
return False
return (self.section_id == section.section_id and
self.name == section.name and
(self.plex_type == section.plex_type if self.plex_type else
self.section_type == section.section_type))
def __ne__(self, section):
return not self == section
@property
def section_id(self):
return self._section_id
@section_id.setter
def section_id(self, value):
self._section_id = value
self._path = path_ops.path.join(LIBRARY_PATH, 'Plex-%s' % value, '')
self._playlist_path = path_ops.path.join(PLAYLISTS_PATH,
'Plex %s.xsp' % value)
@property
def section_type(self):
return self._section_type
@section_type.setter
def section_type(self, value):
self._section_type = value
self.content = v.CONTENT_FROM_PLEX_TYPE[value]
# Default values whether we sync or not based on the Plex type
if value == v.PLEX_TYPE_PHOTO:
self.sync_to_kodi = False
elif not app.SYNC.enable_music and value == v.PLEX_TYPE_ARTIST:
self.sync_to_kodi = False
else:
self.sync_to_kodi = True
@property
def plex_type(self):
return self._plex_type
@plex_type.setter
def plex_type(self, value):
self._plex_type = value
self.section_type_text = utils.lang(v.TRANSLATION_FROM_PLEXTYPE[value])
@property
def index(self):
return self._index
@index.setter
def index(self, value):
self._index = value
self._node = 'Plex.nodes.%s' % value
@property
def node(self):
return self._node
@property
def path(self):
return self._path
@property
def playlist_path(self):
return self._playlist_path
def from_db_element(self, section_db_element):
self.section_id = section_db_element['section_id']
self.name = section_db_element['section_name']
self.section_type = section_db_element['plex_type']
self.kodi_tagid = section_db_element['kodi_tagid']
self.sync_to_kodi = section_db_element['sync_to_kodi']
self.last_sync = section_db_element['last_sync']
def from_xml(self, xml_element):
"""
Reads section from a PMS xml (Plex id, name, Plex type)
"""
api = API(xml_element)
self.section_id = utils.cast(int, xml_element.get('key'))
self.name = api.title()
self.section_type = api.plex_type
self.icon = api.one_artwork('composite')
self.artwork = api.one_artwork('art')
self.thumb = api.one_artwork('thumb')
self.xml = xml_element
def from_plex_db(self, section_id, plexdb=None):
"""
Reads section with id section_id from the plex.db
"""
if plexdb:
section = plexdb.section(section_id)
else:
with PlexDB(lock=False) as plexdb:
section = plexdb.section(section_id)
if section:
self.from_db_element(section)
def to_plex_db(self, plexdb=None):
"""
Writes this Section to the plex.db, potentially overwriting
(INSERT OR REPLACE)
"""
if not self:
raise RuntimeError('Section not clearly defined: %s' % self)
if plexdb:
plexdb.add_section(self.section_id,
self.name,
self.section_type,
self.kodi_tagid,
self.sync_to_kodi,
self.last_sync)
else:
with PlexDB(lock=False) as plexdb:
plexdb.add_section(self.section_id,
self.name,
self.section_type,
self.kodi_tagid,
self.sync_to_kodi,
self.last_sync)
def addon_path(self, args):
"""
Returns the plugin path pointing back to PKC for key in order to browse
args is a dict. Its values may contain string info of the form
{key: '{self.<Section attribute>}'}
"""
args = copy.deepcopy(args)
for key, value in args.iteritems():
args[key] = value.format(self=self)
return utils.extend_url('plugin://%s' % v.ADDON_ID, args)
def to_kodi(self):
"""
Writes this section's nodes to the library folder in the Kodi userdata
directory
Won't do anything if self.sync_to_kodi is not True
"""
if self.index is None:
raise RuntimeError('Index not initialized')
# Main list entry for this section - which will show the different
# nodes as "submenus" once the user navigates into this section
if self.sync_to_kodi and self.section_type in v.PLEX_VIDEOTYPES:
# Node showing a menu for this section
args = {
'mode': 'show_section',
'section_index': self.index
}
index = utils.extend_url('plugin://%s' % v.ADDON_ID, args)
# Node directly displaying all content
path = 'library://video/Plex-{0}/{0}_all.xml'
path = path.format(self.section_id)
else:
# Node showing a menu for this section
args = {
'mode': 'browseplex',
'key': '/library/sections/%s' % self.section_id,
'section_id': unicode(self.section_id)
}
if not self.sync_to_kodi:
args['synched'] = 'false'
# No library xmls to speed things up
# Immediately show the PMS options for this section
index = self.addon_path(args)
# Node directly displaying all content
args = {
'mode': 'browseplex',
'key': '/library/sections/%s/all' % self.section_id,
'section_id': unicode(self.section_id)
}
if not self.sync_to_kodi:
args['synched'] = 'false'
path = self.addon_path(args)
utils.window('%s.index' % self.node, value=index)
utils.window('%s.title' % self.node, value=self.name)
utils.window('%s.type' % self.node, value=self.content)
utils.window('%s.content' % self.node, value=index)
# .path leads to all elements of this library
if self.section_type in v.PLEX_VIDEOTYPES:
utils.window('%s.path' % self.node,
value='ActivateWindow(videos,%s,return)' % path)
elif self.section_type == v.PLEX_TYPE_ARTIST:
utils.window('%s.path' % self.node,
value='ActivateWindow(music,%s,return)' % path)
else:
# Pictures
utils.window('%s.path' % self.node,
value='ActivateWindow(pictures,%s,return)' % path)
utils.window('%s.id' % self.node, value=str(self.section_id))
if not self.sync_to_kodi:
self.remove_files_from_kodi()
return
if self.section_type == v.PLEX_TYPE_ARTIST:
# Todo: Write window variables for music
return
if self.section_type == v.PLEX_TYPE_PHOTO:
# Todo: Write window variables for photos
return
# Create a dedicated directory for this section
if not path_ops.exists(self.path):
path_ops.makedirs(self.path)
# Create a tag just like the section name in the Kodi DB
with kodi_db.KodiVideoDB(lock=False) as kodidb:
self.kodi_tagid = kodidb.create_tag(self.name)
# The xmls are numbered in order of appearance
self.order = 0
if not path_ops.exists(path_ops.path.join(self.path, 'index.xml')):
LOG.debug('Creating index.xml for section %s', self.name)
xml = etree.Element('node',
attrib={'order': unicode(self.order)})
etree.SubElement(xml, 'label').text = self.name
etree.SubElement(xml, 'icon').text = self.icon or nodes.ICON_PATH
self._write_xml(xml, 'index.xml')
self.order += 1
# Create the one smart playlist for this section
if not path_ops.exists(self.playlist_path):
self._write_playlist()
# Now build all nodes for this section - potentially creating xmls
for node in nodes.NODE_TYPES[self.section_type]:
self._build_node(*node)
def _build_node(self, node_type, node_name, args, content, pms_node):
self.content = content
node_name = node_name.format(self=self)
if pms_node:
# Do NOT write a Kodi video library xml - can't use type="filter"
# to point back to plugin://plugin.video.plexkodiconnect
xml = nodes.node_pms(self, node_name, args)
args.pop('folder', None)
path = self.addon_path(args)
else:
# Write a Kodi video library xml
xml_name = '%s_%s.xml' % (self.section_id, node_type)
path = path_ops.path.join(self.path, xml_name)
if not path_ops.exists(path):
# Let's use Kodi's logic to sort/filter the Kodi library
xml = getattr(nodes, 'node_%s' % node_type)(self, node_name)
self._write_xml(xml, xml_name)
path = 'library://video/Plex-%s/%s' % (self.section_id, xml_name)
self.order += 1
self._window_node(path, node_name, node_type, pms_node)
def _write_xml(self, xml, xml_name):
LOG.debug('Creating xml for section %s: %s', self.name, xml_name)
utils.indent(xml)
etree.ElementTree(xml).write(path_ops.path.join(self.path, xml_name),
encoding='utf-8',
xml_declaration=True)
def _write_playlist(self):
LOG.debug('Creating smart playlist for section %s: %s',
self.name, self.playlist_path)
xml = etree.Element('smartplaylist',
attrib={'type': v.CONTENT_FROM_PLEX_TYPE[self.section_type]})
etree.SubElement(xml, 'name').text = self.name
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
'operator': 'is'})
etree.SubElement(rule, 'value').text = self.name
utils.indent(xml)
etree.ElementTree(xml).write(self.playlist_path, encoding='utf-8')
def _window_node(self, path, node_name, node_type, pms_node):
"""
Will save this section's node to the Kodi window variables
Uses the same conventions/logic as Emby for Kodi does
"""
if pms_node or not self.sync_to_kodi:
# Check: elif node_type in ('browse', 'homevideos', 'photos'):
window_path = path
elif self.section_type == v.PLEX_TYPE_ARTIST:
window_path = 'ActivateWindow(Music,%s,return)' % path
else:
window_path = 'ActivateWindow(Videos,%s,return)' % path
# if node_type == 'all':
# var = self.node
# utils.window('%s.index' % var,
# value=path.replace('%s_all.xml' % self.section_id, ''))
# utils.window('%s.title' % var, value=self.name)
# else:
var = '%s.%s' % (self.node, node_type)
utils.window('%s.index' % var, value=path)
utils.window('%s.title' % var, value=node_name)
utils.window('%s.id' % var, value=str(self.section_id))
utils.window('%s.path' % var, value=window_path)
utils.window('%s.type' % var, value=self.content)
utils.window('%s.content' % var, value=path)
utils.window('%s.artwork' % var, value=self.artwork)
def remove_files_from_kodi(self):
"""
Removes this sections from the Kodi userdata library folder (if appl.)
Also removes the smart playlist
"""
if self.section_type in (v.PLEX_TYPE_ARTIST, v.PLEX_TYPE_PHOTO):
# No files created for these types
return
if path_ops.exists(self.path):
path_ops.rmtree(self.path, ignore_errors=True)
if path_ops.exists(self.playlist_path):
try:
path_ops.remove(self.playlist_path)
except (OSError, IOError):
LOG.warn('Could not delete smart playlist for section %s: %s',
self.name, self.playlist_path)
def remove_window_vars(self):
"""
Removes all windows variables 'Plex.nodes.<section_id>.xxx'
"""
if self.index is not None:
_clear_window_vars(self.index)
def remove_from_plex(self, plexdb=None):
"""
Removes this sections completely from the Plex DB
"""
if plexdb:
plexdb.remove_section(self.section_id)
else:
with PlexDB(lock=False) as plexdb:
plexdb.remove_section(self.section_id)
def remove(self):
"""
Completely and utterly removes this section from Kodi and Plex DB
as well as from the window variables
"""
self.remove_files_from_kodi()
self.remove_window_vars()
self.remove_from_plex()
def _get_children(plex_type):
if plex_type == v.PLEX_TYPE_ALBUM:
return True
else:
return False
def get_sync_section(section, plex_type):
"""
Deep-copies section and adds certain arguments in order to prep section
for the library sync
"""
section = copy.deepcopy(section)
section.plex_type = plex_type
section.context = itemtypes.ITEMTYPE_FROM_PLEXTYPE[plex_type]
section.get_children = _get_children(plex_type)
# Some more init stuff
# Has sync for this section been successful?
section.sync_successful = True
# List of tuples: (collection index [as in an item's metadata with
# "Collection id"], collection plex id)
section.collection_match = None
# Dict with entries of the form <collection index>: <collection xml>
section.collection_xmls = {}
# Keep count during sync
section.count = 0
# Total number of items that we need to sync
section.number_of_items = 0
# Iterator to get one sync item after the other
section.iterator = None
return section
def force_full_sync():
"""
Resets the sync timestamp for all sections to 0, thus forcing a subsequent
full sync (not delta)
"""
LOG.info('Telling PKC to do a full sync instead of a delta sync')
with PlexDB() as plexdb:
plexdb.force_full_sync()
def _save_sections_to_plex_db(sections):
with PlexDB() as plexdb:
for section in sections:
section.to_plex_db(plexdb=plexdb)
def _retrieve_old_settings(sections, old_sections):
"""
Overwrites the PKC settings for sections, grabing them from old_sections
if a particular section is in both sections and old_sections
Thus sets to the old values:
section.last_sync
section.kodi_tagid
section.sync_to_kodi
section.last_sync
"""
for section in sections:
for old_section in old_sections:
if section == old_section:
section.last_sync = old_section.last_sync
section.kodi_tagid = old_section.kodi_tagid
section.sync_to_kodi = old_section.sync_to_kodi
section.last_sync = old_section.last_sync
def _delete_kodi_db_items(section):
if section.section_type == v.PLEX_TYPE_MOVIE:
kodi_context = kodi_db.KodiVideoDB
types = ((v.PLEX_TYPE_MOVIE, itemtypes.Movie), )
elif section.section_type == v.PLEX_TYPE_SHOW:
kodi_context = kodi_db.KodiVideoDB
types = ((v.PLEX_TYPE_SHOW, itemtypes.Show),
(v.PLEX_TYPE_SEASON, itemtypes.Season),
(v.PLEX_TYPE_EPISODE, itemtypes.Episode))
elif section.section_type == v.PLEX_TYPE_ARTIST:
kodi_context = kodi_db.KodiMusicDB
types = ((v.PLEX_TYPE_ARTIST, itemtypes.Artist),
(v.PLEX_TYPE_ALBUM, itemtypes.Album),
(v.PLEX_TYPE_SONG, itemtypes.Song))
else:
types = ()
LOG.debug('Skipping deletion of DB elements for section %s', section)
for plex_type, context in types:
while True:
with PlexDB() as plexdb:
plex_ids = list(plexdb.plexid_by_sectionid(section.section_id,
plex_type,
BATCH_SIZE))
with kodi_context(texture_db=True) as kodidb:
typus = context(None, plexdb=plexdb, kodidb=kodidb)
for plex_id in plex_ids:
if SHOULD_CANCEL():
return False
typus.remove(plex_id)
if len(plex_ids) < BATCH_SIZE:
break
return True
def _choose_libraries(sections):
"""
Displays a dialog for the user to select the libraries he wants synched
Returns True if the user chose new sections, False if he aborted
"""
import xbmcgui
selectable_sections = []
preselected = []
index = 0
for section in sections:
if not app.SYNC.enable_music and section.section_type == v.PLEX_TYPE_ARTIST:
LOG.info('Ignoring music section: %s', section)
continue
elif section.section_type == v.PLEX_TYPE_PHOTO:
# We won't ever show Photo sections
continue
else:
# Offer user the new section
selectable_sections.append(section.name)
# Sections have been either preselected by the user or they are new
if section.sync_to_kodi:
preselected.append(index)
index += 1
# Don't ask the user again for this PMS even if user cancel the sync dialog
utils.settings('sections_asked_for_machine_identifier',
value=app.CONN.machine_identifier)
# "Select Plex libraries to sync"
selected_sections = xbmcgui.Dialog().multiselect(utils.lang(30524),
selectable_sections,
preselect=preselected,
useDetails=False)
if selected_sections is None:
LOG.info('User chose not to select which libraries to sync')
return False
index = 0
for section in sections:
if not app.SYNC.enable_music and section.section_type == v.PLEX_TYPE_ARTIST:
continue
elif section.section_type == v.PLEX_TYPE_PHOTO:
continue
else:
section.sync_to_kodi = index in selected_sections
index += 1
return True
def delete_playlists():
"""
Clean up the playlists
"""
path = path_ops.translate_path('special://profile/playlists/video/')
for root, _, files in path_ops.walk(path):
for file in files:
if file.startswith('Plex'):
path_ops.remove(path_ops.path.join(root, file))
def delete_nodes():
"""
Clean up video nodes
"""
path = path_ops.translate_path("special://profile/library/video/")
for root, dirs, _ in path_ops.walk(path):
for directory in dirs:
if directory.startswith('Plex-'):
path_ops.rmtree(path_ops.path.join(root, directory))
break
def delete_files():
"""
Deletes both all the Plex-xxx video node xmls as well as smart playlists
"""
delete_nodes()
delete_playlists()
def sync_from_pms(parent_self, pick_libraries=False):
"""
Sync the Plex library sections.
pick_libraries=True will prompt the user the select the libraries he
wants to sync
"""
global SHOULD_CANCEL
LOG.info('Starting synching sections from the PMS')
SHOULD_CANCEL = parent_self.should_cancel
try:
return _sync_from_pms(pick_libraries)
finally:
SHOULD_CANCEL = None
LOG.info('Done synching sections from the PMS: %s', app.SYNC.sections)
def _sync_from_pms(pick_libraries):
# Re-set value in order to make sure we got the lastest user input
app.SYNC.enable_music = utils.settings('enableMusic') == 'true'
xml = PF.get_plex_sections()
if xml is None:
LOG.error("Error download PMS sections, abort")
return False
sections = []
old_sections = []
for i, xml_element in enumerate(xml.findall('Directory')):
api = API(xml_element)
if api.plex_type in v.UNSUPPORTED_PLEX_TYPES:
continue
sections.append(Section(index=i, xml_element=xml_element))
with PlexDB() as plexdb:
for section_db in plexdb.all_sections():
old_sections.append(Section(section_db_element=section_db))
# Update our latest PMS sections with info saved in the PMS DB
_retrieve_old_settings(sections, old_sections)
if (app.CONN.machine_identifier != utils.settings('sections_asked_for_machine_identifier') or
pick_libraries):
if not pick_libraries:
LOG.info('First time connecting to this PMS, choosing libraries')
_choose_libraries(sections)
# We got everything - save to Plex db in case Kodi restarts before we're
# done here
_save_sections_to_plex_db(sections)
# Tweak some settings so Kodi does NOT scan the music folders
if app.SYNC.direct_paths is True:
# Will reboot Kodi is new library detected
music.excludefromscan_music_folders(sections)
# Delete all old sections that are obsolete
# This will also delete sections whose name (or type) have changed
for old_section in old_sections:
for section in sections:
if old_section == section:
break
else:
if not old_section.sync_to_kodi:
continue
LOG.info('Deleting entire section: %s', old_section)
# Remove all linked items
if not _delete_kodi_db_items(old_section):
return False
# Remove the section itself
old_section.remove()
# Clear all existing window vars because we did NOT remove them with the
# command section.remove()
clear_window_vars()
# Time to write the sections to Kodi
for section in sections:
section.to_kodi()
# Counter that tells us how many sections we have - e.g. for skins and
# listings
utils.window('Plex.nodes.total', str(len(sections)))
app.SYNC.sections = sections
return True
def _clear_window_vars(index):
node = 'Plex.nodes.%s' % index
utils.window('%s.index' % node, clear=True)
utils.window('%s.title' % node, clear=True)
utils.window('%s.type' % node, clear=True)
utils.window('%s.content' % node, clear=True)
utils.window('%s.path' % node, clear=True)
utils.window('%s.id' % node, clear=True)
# Just clear everything here, ignore the plex_type
for typus in (x[0] for y in nodes.NODE_TYPES.values() for x in y):
for kind in WINDOW_ARGS:
node = 'Plex.nodes.%s.%s.%s' % (index, typus, kind)
utils.window(node, clear=True)
def clear_window_vars():
"""
Removes all references to sections stored in window vars 'Plex.nodes...'
"""
LOG.debug('Clearing all the Plex video node variables')
number_of_nodes = int(utils.window('Plex.nodes.total') or 0)
utils.window('Plex.nodes.total', clear=True)
for index in range(number_of_nodes):
_clear_window_vars(index)
def delete_videonode_files():
"""
Removes all the PKC video node files under userdata/library/video that
start with 'Plex-'
"""
for root, dirs, _ in path_ops.walk(LIBRARY_PATH):
for directory in dirs:
if directory.startswith('Plex-'):
abs_path = path_ops.path.join(root, directory)
LOG.info('Removing video node directory %s', abs_path)
path_ops.rmtree(abs_path, ignore_errors=True)
break

View file

@ -1,71 +0,0 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from threading import Thread, Lock
from xbmc import sleep
from xbmcgui import DialogProgressBG
from utils import thread_methods, language as lang
###############################################################################
LOG = getLogger("PLEX." + __name__)
GET_METADATA_COUNT = 0
PROCESS_METADATA_COUNT = 0
PROCESSING_VIEW_NAME = ''
LOCK = Lock()
###############################################################################
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
'STOP_SYNC',
'SUSPEND_SYNC'])
class ThreadedShowSyncInfo(Thread):
"""
Threaded class to show the Kodi statusbar of the metadata download.
Input:
total: Total number of items to get
item_type:
"""
def __init__(self, total, item_type):
self.total = total
self.item_type = item_type
Thread.__init__(self)
def run(self):
"""
Do the work
"""
LOG.debug('Show sync info thread started')
# cache local variables because it's faster
total = self.total
dialog = DialogProgressBG('dialoglogProgressBG')
dialog.create("%s %s: %s %s"
% (lang(39714), self.item_type, str(total), lang(39715)))
total = 2 * total
total_progress = 0
while not self.stopped():
with LOCK:
get_progress = GET_METADATA_COUNT
process_progress = PROCESS_METADATA_COUNT
view_name = PROCESSING_VIEW_NAME
total_progress = get_progress + process_progress
try:
percentage = int(float(total_progress) / float(total)*100.0)
except ZeroDivisionError:
percentage = 0
dialog.update(percentage,
message="%s %s. %s %s: %s"
% (get_progress,
lang(39712),
process_progress,
lang(39713),
view_name))
# Sleep for x milliseconds
sleep(200)
dialog.close()
LOG.debug('Show sync info thread terminated')

View file

@ -0,0 +1,372 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .common import update_kodi_library, PLAYLIST_SYNC_ENABLED
from .fanart import SYNC_FANART, FanartTask
from ..plex_api import API
from ..plex_db import PlexDB
from .. import kodi_db
from .. import backgroundthread, plex_functions as PF, itemtypes
from .. import artwork, utils, timing, variables as v, app
if PLAYLIST_SYNC_ENABLED:
from .. import playlists
LOG = getLogger('PLEX.sync.websocket')
CACHING_ENALBED = utils.settings('enableTextureCache') == "true"
WEBSOCKET_MESSAGES = []
# Dict to save info for Plex items currently being played somewhere
PLAYSTATE_SESSIONS = {}
def multi_delete(input_list, delete_list):
"""
Deletes the list items of input_list at the positions in delete_list
(which can be in any arbitrary order)
"""
for index in sorted(delete_list, reverse=True):
del input_list[index]
return input_list
def store_websocket_message(message):
"""
processes json.loads() messages from websocket. Triage what we need to
do with "process_" methods
"""
if message['type'] == 'playing':
process_playing(message['PlaySessionStateNotification'])
elif message['type'] == 'timeline':
store_timeline_message(message['TimelineEntry'])
elif message['type'] == 'activity':
store_activity_message(message['ActivityNotification'])
def process_websocket_messages():
"""
Periodically called to process new/updated PMS items
PMS needs a while to download info from internet AFTER it
showed up under 'timeline' websocket messages
data['type']:
1: movie
2: tv show??
3: season??
4: episode
8: artist (band)
9: album
10: track (song)
12: trailer, extras?
data['state']:
0: 'created',
2: 'matching',
3: 'downloading',
4: 'loading',
5: 'finished',
6: 'analyzing',
9: 'deleted'
"""
global WEBSOCKET_MESSAGES
now = timing.unix_timestamp()
update_kodi_video_library, update_kodi_music_library = False, False
delete_list = []
for i, message in enumerate(WEBSOCKET_MESSAGES):
if message['state'] == 9:
successful, video, music = process_delete_message(message)
elif now - message['timestamp'] < app.SYNC.backgroundsync_saftymargin:
# We haven't waited long enough for the PMS to finish processing the
# item. Do it later (excepting deletions)
continue
else:
successful, video, music = process_new_item_message(message)
if (successful and SYNC_FANART and
message['plex_type'] in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
task = FanartTask()
task.setup(message['plex_id'],
message['plex_type'],
refresh=False)
backgroundthread.BGThreader.addTask(task)
if successful is True:
delete_list.append(i)
update_kodi_video_library = True if video else update_kodi_video_library
update_kodi_music_library = True if music else update_kodi_music_library
else:
# Safety net if we can't process an item
message['attempt'] += 1
if message['attempt'] > 3:
LOG.error('Repeatedly could not process message %s, abort',
message)
delete_list.append(i)
# Get rid of the items we just processed
if delete_list:
WEBSOCKET_MESSAGES = multi_delete(WEBSOCKET_MESSAGES, delete_list)
# Let Kodi know of the change
if update_kodi_video_library or update_kodi_music_library:
update_kodi_library(video=update_kodi_video_library,
music=update_kodi_music_library)
def process_new_item_message(message):
LOG.debug('Message: %s', message)
xml = PF.GetPlexMetadata(message['plex_id'])
try:
plex_type = xml[0].attrib['type']
except (IndexError, KeyError, TypeError):
LOG.error('Could not download metadata for %s', message['plex_id'])
return False, False, False
LOG.debug("Processing new/updated PMS item: %s", message['plex_id'])
with itemtypes.ITEMTYPE_FROM_PLEXTYPE[plex_type](timing.unix_timestamp()) as typus:
typus.add_update(xml[0],
section_name=xml.get('librarySectionTitle'),
section_id=utils.cast(int, xml.get('librarySectionID')))
cache_artwork(message['plex_id'], plex_type)
return True, plex_type in v.PLEX_VIDEOTYPES, plex_type in v.PLEX_AUDIOTYPES
def process_delete_message(message):
plex_type = message['plex_type']
with itemtypes.ITEMTYPE_FROM_PLEXTYPE[plex_type](None) as typus:
typus.remove(message['plex_id'], plex_type=plex_type)
return True, plex_type in v.PLEX_VIDEOTYPES, plex_type in v.PLEX_AUDIOTYPES
def store_timeline_message(data):
"""
PMS is messing with the library items, e.g. new or changed. Put in our
"processing queue" for later
"""
global WEBSOCKET_MESSAGES
for message in data:
if 'tv.plex' in message.get('identifier', ''):
# Ommit Plex DVR messages - the Plex IDs are not corresponding
# (DVR ratingKeys are not unique and might correspond to a
# movie or episode)
continue
try:
typus = v.PLEX_TYPE_FROM_WEBSOCKET[int(message['type'])]
except KeyError:
# E.g. -1 - thanks Plex!
LOG.info('Ignoring invalid message %s', data)
continue
if typus in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_SET):
# No need to process extras or trailers
continue
status = int(message['state'])
if typus == 'playlist' and PLAYLIST_SYNC_ENABLED:
playlists.websocket(plex_id=unicode(message['itemID']),
status=status)
elif status == 9:
# Immediately and always process deletions (as the PMS will
# send additional message with other codes)
WEBSOCKET_MESSAGES.append({
'state': status,
'plex_type': typus,
'plex_id': utils.cast(int, message['itemID']),
'timestamp': timing.unix_timestamp(),
'attempt': 0
})
elif typus in (v.PLEX_TYPE_MOVIE,
v.PLEX_TYPE_EPISODE,
v.PLEX_TYPE_SONG) and status == 5:
plex_id = int(message['itemID'])
# Have we already added this element for processing?
for existing_message in WEBSOCKET_MESSAGES:
if existing_message['plex_id'] == plex_id:
break
else:
# Haven't added this element to the queue yet
WEBSOCKET_MESSAGES.append({
'state': status,
'plex_type': typus,
'plex_id': plex_id,
'timestamp': timing.unix_timestamp(),
'attempt': 0
})
def store_activity_message(data):
"""
PMS is re-scanning an item, e.g. after having changed a movie poster.
WATCH OUT for this if it's triggered by our PKC library scan!
"""
global WEBSOCKET_MESSAGES
for message in data:
if message['event'] != 'ended':
# Scan still going on, so skip for now
continue
elif message['Activity'].get('Context') is None:
# Not related to any Plex element, but entire library
continue
elif message['Activity']['type'] != 'library.refresh.items':
# Not the type of message relevant for us
continue
plex_id = PF.GetPlexKeyNumber(message['Activity']['Context']['key'])[1]
if not plex_id:
# Likely a Plex id like /library/metadata/3/children
continue
# We're only looking at existing elements - have we synced yet?
with PlexDB(lock=False) as plexdb:
typus = plexdb.item_by_id(plex_id, plex_type=None)
if not typus:
LOG.debug('plex_id %s not synced yet - skipping', plex_id)
continue
# Have we already added this element?
for existing_message in WEBSOCKET_MESSAGES:
if existing_message['plex_id'] == plex_id:
break
else:
# Haven't added this element to the queue yet
WEBSOCKET_MESSAGES.append({
'state': None, # Don't need a state here
'plex_type': typus['plex_type'],
'plex_id': plex_id,
'timestamp': timing.unix_timestamp(),
'attempt': 0
})
def process_playing(data):
"""
Someone (not necessarily the user signed in) is playing something some-
where
"""
global PLAYSTATE_SESSIONS
for message in data:
status = message['state']
if status == 'buffering' or status == 'stopped':
# Drop buffering and stop messages immediately - no value
continue
plex_id = utils.cast(int, message['ratingKey'])
skip = False
for pid in (0, 1, 2):
if plex_id == app.PLAYSTATE.player_states[pid]['plex_id']:
# Kodi is playing this message - no need to set the playstate
skip = True
if skip:
continue
if 'sessionKey' not in message:
LOG.warn('Received malformed message from the PMS: %s', message)
continue
session_key = message['sessionKey']
# Do we already have a sessionKey stored?
if session_key not in PLAYSTATE_SESSIONS:
with PlexDB(lock=False) as plexdb:
typus = plexdb.item_by_id(plex_id, plex_type=None)
if not typus or 'kodi_fileid' not in typus:
# Item not (yet) in Kodi library or not affiliated with a file
continue
if utils.settings('plex_serverowned') == 'false':
# Not our PMS, we are not authorized to get the sessions
# On the bright side, it must be us playing :-)
PLAYSTATE_SESSIONS[session_key] = {}
else:
# PMS is ours - get all current sessions
pms_sessions = PF.GetPMSStatus(app.ACCOUNT.plex_token)
if session_key not in pms_sessions:
LOG.info('Session key %s still unknown! Skip '
'playstate update', session_key)
continue
PLAYSTATE_SESSIONS[session_key] = pms_sessions[session_key]
LOG.debug('Updated current sessions. They are: %s',
PLAYSTATE_SESSIONS)
# Attach Kodi info to the session
PLAYSTATE_SESSIONS[session_key]['kodi_fileid'] = typus['kodi_fileid']
if typus['plex_type'] == v.PLEX_TYPE_EPISODE:
PLAYSTATE_SESSIONS[session_key]['kodi_fileid_2'] = typus['kodi_fileid_2']
else:
PLAYSTATE_SESSIONS[session_key]['kodi_fileid_2'] = None
PLAYSTATE_SESSIONS[session_key]['kodi_id'] = typus['kodi_id']
PLAYSTATE_SESSIONS[session_key]['kodi_type'] = typus['kodi_type']
session = PLAYSTATE_SESSIONS[session_key]
if utils.settings('plex_serverowned') != 'false':
# Identify the user - same one as signed on with PKC? Skip
# update if neither session's username nor userid match
# (Owner sometime's returns id '1', not always)
if not app.ACCOUNT.plex_token and session['userId'] == '1':
# PKC not signed in to plex.tv. Plus owner of PMS is
# playing (the '1').
# Hence must be us (since several users require plex.tv
# token for PKC)
pass
elif not (session['userId'] == app.ACCOUNT.plex_user_id or
session['username'] == app.ACCOUNT.plex_username):
LOG.debug('Our username %s, userid %s did not match '
'the session username %s with userid %s',
app.ACCOUNT.plex_username,
app.ACCOUNT.plex_user_id,
session['username'],
session['userId'])
continue
# Get an up-to-date XML from the PMS because PMS will NOT directly
# tell us: duration of item viewCount
if not session.get('duration'):
xml = PF.GetPlexMetadata(plex_id)
if xml in (None, 401):
LOG.error('Could not get up-to-date xml for item %s',
plex_id)
continue
api = API(xml[0])
session['duration'] = api.runtime()
session['viewCount'] = api.viewcount()
# Sometimes, Plex tells us resume points in milliseconds and
# not in seconds - thank you very much!
if message['viewOffset'] > session['duration']:
resume = message['viewOffset'] / 1000
else:
resume = message['viewOffset']
if resume < v.IGNORE_SECONDS_AT_START:
continue
try:
completed = float(resume) / float(session['duration'])
except (ZeroDivisionError, TypeError):
LOG.error('Could not mark playstate for %s and session %s',
data, session)
continue
if completed >= v.MARK_PLAYED_AT:
# Only mark completely watched ONCE
if session.get('marked_played') is None:
session['marked_played'] = True
mark_played = True
else:
# Don't mark it as completely watched again
continue
else:
mark_played = False
LOG.debug('Update playstate for user %s for %s with plex id %s to '
'viewCount %s, resume %s, mark_played %s for item %s',
app.ACCOUNT.plex_username, session['kodi_type'], plex_id,
session['viewCount'], resume, mark_played, PLAYSTATE_SESSIONS[session_key])
func = itemtypes.ITEMTYPE_FROM_KODITYPE[session['kodi_type']]
with func(None) as fkt:
fkt.update_playstate(mark_played,
session['viewCount'],
resume,
session['duration'],
session['kodi_fileid'],
session['kodi_fileid_2'],
timing.unix_timestamp())
def cache_artwork(plex_id, plex_type, kodi_id=None, kodi_type=None):
"""
Triggers caching of artwork (if so enabled in the PKC settings)
"""
if not CACHING_ENALBED:
return
if not kodi_id:
with PlexDB(lock=False) as plexdb:
item = plexdb.item_by_id(plex_id, plex_type)
if not item:
LOG.error('Could not retrieve Plex db info for %s', plex_id)
return
kodi_id, kodi_type = item['kodi_id'], item['kodi_type']
with kodi_db.KODIDB_FROM_PLEXTYPE[plex_type]() as kodidb:
for url in kodidb.art_urls(kodi_id, kodi_type):
artwork.cache_url(url)

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
from __future__ import absolute_import, division, unicode_literals
import logging
import xbmc
###############################################################################
@ -37,9 +38,11 @@ def config():
class LogHandler(logging.StreamHandler):
def __init__(self):
logging.StreamHandler.__init__(self)
self.setFormatter(logging.Formatter(fmt="%(name)s: %(message)s"))
self.setFormatter(logging.Formatter(fmt=b"%(name)s: %(message)s"))
def emit(self, record):
if isinstance(record.msg, unicode):
record.msg = record.msg.encode('utf-8')
try:
xbmc.log(self.format(record), level=LEVELS[record.levelno])
except UnicodeEncodeError:

View file

@ -1,29 +1,102 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import variables as v
from utils import compare_version, settings
from . import variables as v
from . import utils
###############################################################################
log = getLogger("PLEX."+__name__)
LOG = getLogger('PLEX.migration')
def check_migration():
log.info('Checking whether we need to migrate something')
last_migration = settings('last_migrated_PKC_version')
if last_migration == v.ADDON_VERSION:
log.info('Already migrated to PKC version %s' % v.ADDON_VERSION)
# Ensure later migration if user downgraded PKC!
settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
LOG.info('Checking whether we need to migrate something')
last_migration = utils.settings('last_migrated_PKC_version')
# Ensure later migration if user downgraded PKC!
utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
if last_migration == '':
LOG.info('New, clean PKC installation - no migration necessary')
return
elif last_migration == v.ADDON_VERSION:
LOG.info('Already migrated to PKC version %s' % v.ADDON_VERSION)
return
if not compare_version(v.ADDON_VERSION, '1.8.2'):
log.info('Migrating to version 1.8.1')
if not utils.compare_version(last_migration, '1.8.2'):
LOG.info('Migrating to version 1.8.1')
# Set the new PKC theMovieDB key
settings('themoviedbAPIKey', value='19c90103adb9e98f2172c6a6a3d85dc4')
utils.settings('themoviedbAPIKey',
value='19c90103adb9e98f2172c6a6a3d85dc4')
if not compare_version(v.ADDON_VERSION, '2.0.25'):
log.info('Migrating to version 2.0.24')
if not utils.compare_version(last_migration, '2.0.25'):
LOG.info('Migrating to version 2.0.24')
# Need to re-connect with PMS to pick up on plex.direct URIs
settings('ipaddress', value='')
settings('port', value='')
utils.settings('ipaddress', value='')
utils.settings('port', value='')
settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
if not utils.compare_version(last_migration, '2.7.6'):
LOG.info('Migrating to version 2.7.5')
from .library_sync.sections import delete_files
delete_files()
if not utils.compare_version(last_migration, '2.8.3'):
LOG.info('Migrating to version 2.8.2')
from .library_sync import sections
sections.clear_window_vars()
sections.delete_videonode_files()
if not utils.compare_version(last_migration, '2.8.7'):
LOG.info('Migrating to version 2.8.6')
# Need to delete the UNIQUE index that prevents creating several
# playlist entries with the same kodi_hash
from .plex_db import PlexDB
with PlexDB() as plexdb:
plexdb.cursor.execute('DROP INDEX IF EXISTS ix_playlists_3')
# Index will be automatically recreated on next PKC startup
if not utils.compare_version(last_migration, '2.8.9'):
LOG.info('Migrating to version 2.8.8')
from .library_sync import sections
sections.clear_window_vars()
sections.delete_videonode_files()
if not utils.compare_version(last_migration, '2.9.3'):
LOG.info('Migrating to version 2.9.2')
# Re-sync all playlists to Kodi
from .playlists import remove_synced_playlists
remove_synced_playlists()
if not utils.compare_version(last_migration, '2.9.7'):
LOG.info('Migrating to version 2.9.6')
# Allow for a new "Direct Stream" setting (number 2), so shift the
# last setting for "force transcoding"
current_playback_type = utils.cast(int, utils.settings('playType')) or 0
if current_playback_type == 2:
current_playback_type = 3
utils.settings('playType', value=str(current_playback_type))
if not utils.compare_version(last_migration, '2.9.8'):
LOG.info('Migrating to version 2.9.7')
# Force-scan every single item in the library - seems like we could
# loose some recently added items otherwise
# Caused by 65a921c3cc2068c4a34990d07289e2958f515156
from . import library_sync
library_sync.force_full_sync()
if not utils.compare_version(last_migration, '2.11.3'):
LOG.info('Migrating to version 2.11.2')
# Re-sync all playlists to Kodi
from .playlists import remove_synced_playlists
remove_synced_playlists()
if not utils.compare_version(last_migration, '2.12.2'):
LOG.info('Migrating to version 2.12.1')
# Sign user out to make sure he needs to sign in again
utils.settings('username', value='')
utils.settings('userid', value='')
utils.settings('plex_restricteduser', value='')
utils.settings('accessToken', value='')
utils.settings('plexAvatar', value='')
utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION)

View file

@ -1,52 +1,47 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from re import compile as re_compile
from xml.etree.ElementTree import ParseError
import re
from utils import XmlKodiSetting, reboot_kodi, language as lang
from PlexFunctions import get_plex_sections
from PlexAPI import API
import variables as v
from .plex_api.media import Media
from . import utils
from . import variables as v
###############################################################################
LOG = getLogger("PLEX." + __name__)
REGEX_MUSICPATH = re_compile(r'''^\^(.+)\$$''')
LOG = getLogger('PLEX.music.py')
###############################################################################
def excludefromscan_music_folders():
def excludefromscan_music_folders(sections):
"""
Gets a complete list of paths for music libraries from the PMS. Sets them
to be excluded in the advancedsettings.xml from being scanned by Kodi.
Existing keys will be replaced
xml: etree XML PMS answer containing all library sections
Reboots Kodi if new library detected
"""
xml = get_plex_sections()
try:
xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error('Could not get Plex sections')
return
# Build paths
paths = []
api = API(item=None)
for library in xml:
if library.attrib['type'] != v.PLEX_TYPE_ARTIST:
reboot = False
api = Media()
for section in sections:
if section.section_type != v.PLEX_TYPE_ARTIST:
# Only look at music libraries
continue
for location in library:
if location.tag == 'Location':
path = api.validate_playurl(location.attrib['path'],
typus=v.PLEX_TYPE_ARTIST,
omit_check=True)
paths.append(__turn_to_regex(path))
if not section.sync_to_kodi:
continue
for location in section.xml.findall('Location'):
path = api.validate_playurl(location.attrib['path'],
typus=v.PLEX_TYPE_ARTIST,
omit_check=True)
paths.append(_turn_to_regex(path))
try:
with XmlKodiSetting('advancedsettings.xml',
force_create=True,
top_element='advancedsettings') as xml:
parent = xml.set_setting(['audio', 'excludefromscan'])
with utils.XmlKodiSetting(
'advancedsettings.xml',
force_create=True,
top_element='advancedsettings') as xml_file:
parent = xml_file.set_setting(['audio', 'excludefromscan'])
for path in paths:
for element in parent:
if element.text == path:
@ -54,11 +49,15 @@ def excludefromscan_music_folders():
break
else:
LOG.info('New Plex music library detected: %s', path)
xml.set_setting(['audio', 'excludefromscan', 'regexp'],
value=path, append=True)
# We only need to reboot if we ADD new paths!
reboot = xml.write_xml
xml_file.set_setting(['audio', 'excludefromscan', 'regexp'],
value=path,
append=True)
if paths:
# We only need to reboot if we ADD new paths!
reboot = xml_file.write_xml
# Delete obsolete entries
# Make sure we're not saving an empty audio-excludefromscan
xml_file.write_xml = reboot
for element in parent:
for path in paths:
if element.text == path:
@ -67,16 +66,16 @@ def excludefromscan_music_folders():
LOG.info('Deleting music library from advancedsettings: %s',
element.text)
parent.remove(element)
except (ParseError, IOError):
xml_file.write_xml = True
except (utils.ParseError, IOError):
LOG.error('Could not adjust advancedsettings.xml')
reboot = False
if reboot is True:
# 'New Plex music library detected. Sorry, but we need to
# restart Kodi now due to the changes made.'
reboot_kodi(lang(39711))
utils.reboot_kodi(utils.lang(39711))
def __turn_to_regex(path):
def _turn_to_regex(path):
"""
Turns a path into regex expression to be fed to Kodi's advancedsettings.xml
"""
@ -87,7 +86,7 @@ def __turn_to_regex(path):
else:
if not path.endswith('\\'):
path = '%s\\' % path
# Need to escape backslashes
path = path.replace('\\', '\\\\')
# Escape all characters that could cause problems
path = re.escape(path)
# Beginning of path only needs to be similar
return '^%s' % path

243
resources/lib/path_ops.py Normal file
View file

@ -0,0 +1,243 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
File and Path operations
Kodi xbmc*.*() functions usually take utf-8 encoded commands, thus try_encode
works.
Unfortunatly, working with filenames and paths seems to require an encoding in
the OS' getfilesystemencoding - it will NOT always work with unicode paths.
However, sys.getfilesystemencoding might return None.
Feed unicode to all the functions below and you're fine.
WARNING: os.path won't really work with smb paths (possibly others). For
xbmcvfs functions to work with smb paths, they need to be both in passwords.xml
as well as sources.xml
"""
from __future__ import absolute_import, division, unicode_literals
import shutil
import os
from os import path # allows to use path_ops.path.join, for example
from distutils import dir_util
import re
import xbmc
import xbmcvfs
from .tools import unicode_paths
# Kodi seems to encode in utf-8 in ALL cases (unlike e.g. the OS filesystem)
KODI_ENCODING = 'utf-8'
REGEX_FILE_NUMBERING = re.compile(r'''_(\d\d)\.\w+$''')
def encode_path(path):
"""
Filenames and paths are not necessarily utf-8 encoded. Use this function
instead of try_encode/trydecode if working with filenames and paths!
(os.walk only feeds on encoded paths. sys.getfilesystemencoding returns None
for Raspberry Pi)
"""
return unicode_paths.encode(path)
def decode_path(path):
"""
Filenames and paths are not necessarily utf-8 encoded. Use this function
instead of try_encode/trydecode if working with filenames and paths!
(os.walk only feeds on encoded paths. sys.getfilesystemencoding returns None
for Raspberry Pi)
"""
return unicode_paths.decode(path)
def translate_path(path):
"""
Returns the XBMC translated path [unicode]
e.g. Converts 'special://masterprofile/script_data'
-> '/home/user/XBMC/UserData/script_data' on Linux.
"""
translated = xbmc.translatePath(path.encode(KODI_ENCODING, 'strict'))
return translated.decode(KODI_ENCODING, 'strict')
def exists(path):
"""
Returns True if the path [unicode] exists. Folders NEED a trailing slash or
backslash!!
"""
return xbmcvfs.exists(path.encode(KODI_ENCODING, 'strict')) == 1
def rmtree(path, *args, **kwargs):
"""Recursively delete a directory tree.
If ignore_errors is set, errors are ignored; otherwise, if onerror
is set, it is called to handle the error with arguments (func,
path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
path is the argument to that function that caused it to fail; and
exc_info is a tuple returned by sys.exc_info(). If ignore_errors
is false and onerror is None, an exception is raised.
"""
return shutil.rmtree(encode_path(path), *args, **kwargs)
def copyfile(src, dst):
"""Copy data from src to dst"""
return shutil.copyfile(encode_path(src), encode_path(dst))
def makedirs(path, *args, **kwargs):
"""makedirs(path [, mode=0777])
Super-mkdir; create a leaf directory and all intermediate ones. Works like
mkdir, except that any intermediate path segment (not just the rightmost)
will be created if it does not exist. This is recursive.
"""
return os.makedirs(encode_path(path), *args, **kwargs)
def remove(path):
"""
Remove (delete) the file path. If path is a directory, OSError is raised;
see rmdir() below to remove a directory. This is identical to the unlink()
function documented below. On Windows, attempting to remove a file that is
in use causes an exception to be raised; on Unix, the directory entry is
removed but the storage allocated to the file is not made available until
the original file is no longer in use.
"""
return os.remove(encode_path(path))
def walk(top, topdown=True, onerror=None, followlinks=False):
"""
Directory tree generator.
For each directory in the directory tree rooted at top (including top
itself, but excluding '.' and '..'), yields a 3-tuple
dirpath, dirnames, filenames
dirpath is a string, the path to the directory. dirnames is a list of
the names of the subdirectories in dirpath (excluding '.' and '..').
filenames is a list of the names of the non-directory files in dirpath.
Note that the names in the lists are just names, with no path components.
To get a full path (which begins with top) to a file or directory in
dirpath, do os.path.join(dirpath, name).
If optional arg 'topdown' is true or not specified, the triple for a
directory is generated before the triples for any of its subdirectories
(directories are generated top down). If topdown is false, the triple
for a directory is generated after the triples for all of its
subdirectories (directories are generated bottom up).
When topdown is true, the caller can modify the dirnames list in-place
(e.g., via del or slice assignment), and walk will only recurse into the
subdirectories whose names remain in dirnames; this can be used to prune the
search, or to impose a specific order of visiting. Modifying dirnames when
topdown is false is ineffective, since the directories in dirnames have
already been generated by the time dirnames itself is generated. No matter
the value of topdown, the list of subdirectories is retrieved before the
tuples for the directory and its subdirectories are generated.
By default errors from the os.listdir() call are ignored. If
optional arg 'onerror' is specified, it should be a function; it
will be called with one argument, an os.error instance. It can
report the error to continue with the walk, or raise the exception
to abort the walk. Note that the filename is available as the
filename attribute of the exception object.
By default, os.walk does not follow symbolic links to subdirectories on
systems that support them. In order to get this functionality, set the
optional argument 'followlinks' to true.
Caution: if you pass a relative pathname for top, don't change the
current working directory between resumptions of walk. walk never
changes the current directory, and assumes that the client doesn't
either.
Example:
import os
from os.path import join, getsize
for root, dirs, files in os.walk('python/Lib/email'):
print root, "consumes",
print sum([getsize(join(root, name)) for name in files]),
print "bytes in", len(files), "non-directory files"
if 'CVS' in dirs:
dirs.remove('CVS') # don't visit CVS directories
"""
# Get all the results from os.walk and store them in a list
walker = list(os.walk(encode_path(top),
topdown,
onerror,
followlinks))
for top, dirs, nondirs in walker:
yield (decode_path(top),
[decode_path(x) for x in dirs],
[decode_path(x) for x in nondirs])
def copy_tree(src, dst, *args, **kwargs):
"""
Copy an entire directory tree 'src' to a new location 'dst'.
Both 'src' and 'dst' must be directory names. If 'src' is not a
directory, raise DistutilsFileError. If 'dst' does not exist, it is
created with 'mkpath()'. The end result of the copy is that every
file in 'src' is copied to 'dst', and directories under 'src' are
recursively copied to 'dst'. Return the list of files that were
copied or might have been copied, using their output name. The
return value is unaffected by 'update' or 'dry_run': it is simply
the list of all files under 'src', with the names changed to be
under 'dst'.
'preserve_mode' and 'preserve_times' are the same as for
'copy_file'; note that they only apply to regular files, not to
directories. If 'preserve_symlinks' is true, symlinks will be
copied as symlinks (on platforms that support them!); otherwise
(the default), the destination of the symlink will be copied.
'update' and 'verbose' are the same as for 'copy_file'.
"""
src = encode_path(src)
dst = encode_path(dst)
return dir_util.copy_tree(src, dst, *args, **kwargs)
def basename(path):
"""
Returns the filename for path [unicode] or an empty string if not possible.
Safer than using os.path.basename, as we could be expecting \\ for / or
vice versa
"""
try:
return path.rsplit('/', 1)[1]
except IndexError:
try:
return path.rsplit('\\', 1)[1]
except IndexError:
return ''
def create_unique_path(directory, filename, extension):
"""
Checks whether 'directory/filename.extension' exists. If so, will start
numbering the filename until the file does not exist yet (up to 99)
"""
res = path.join(directory, '.'.join((filename, extension)))
while exists(res):
occurance = REGEX_FILE_NUMBERING.search(res)
if not occurance:
filename = '{}_00'.format(filename[:min(len(filename),
251 - len(extension))])
res = path.join(directory, '.'.join((filename, extension)))
else:
number = int(occurance.group(1)) + 1
if number > 99:
raise RuntimeError('Could not create unique file: {} {} {}'.format(
directory, filename, extension))
basename = re.sub(REGEX_FILE_NUMBERING, '', res)
res = '{}_{:02d}.{}'.format(basename, number, extension)
return res

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# pathtools: File system path tools.
# Copyright (C) 2010 Yesudeep Mangalapilly <yesudeep@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

View file

@ -0,0 +1,206 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# path.py: Path functions.
#
# Copyright (C) 2010 Yesudeep Mangalapilly <yesudeep@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
:module: pathtools.path
:synopsis: Directory walking, listing, and path sanitizing functions.
:author: Yesudeep Mangalapilly <yesudeep@gmail.com>
Functions
---------
.. autofunction:: get_dir_walker
.. autofunction:: walk
.. autofunction:: listdir
.. autofunction:: list_directories
.. autofunction:: list_files
.. autofunction:: absolute_path
.. autofunction:: real_absolute_path
.. autofunction:: parent_dir_path
"""
import os.path
from functools import partial
__all__ = [
'get_dir_walker',
'walk',
'listdir',
'list_directories',
'list_files',
'absolute_path',
'real_absolute_path',
'parent_dir_path',
]
def get_dir_walker(recursive, topdown=True, followlinks=False):
"""
Returns a recursive or a non-recursive directory walker.
:param recursive:
``True`` produces a recursive walker; ``False`` produces a non-recursive
walker.
:returns:
A walker function.
"""
if recursive:
walk = partial(os.walk, topdown=topdown, followlinks=followlinks)
else:
def walk(path, topdown=topdown, followlinks=followlinks):
try:
yield next(os.walk(path, topdown=topdown, followlinks=followlinks))
except NameError:
yield os.walk(path, topdown=topdown, followlinks=followlinks).next() #IGNORE:E1101
return walk
def walk(dir_pathname, recursive=True, topdown=True, followlinks=False):
"""
Walks a directory tree optionally recursively. Works exactly like
:func:`os.walk` only adding the `recursive` argument.
:param dir_pathname:
The directory to traverse.
:param recursive:
``True`` for walking recursively through the directory tree;
``False`` otherwise.
:param topdown:
Please see the documentation for :func:`os.walk`
:param followlinks:
Please see the documentation for :func:`os.walk`
"""
walk_func = get_dir_walker(recursive, topdown, followlinks)
for root, dirnames, filenames in walk_func(dir_pathname):
yield (root, dirnames, filenames)
def listdir(dir_pathname,
recursive=True,
topdown=True,
followlinks=False):
"""
Enlists all items using their absolute paths in a directory, optionally
recursively.
:param dir_pathname:
The directory to traverse.
:param recursive:
``True`` for walking recursively through the directory tree;
``False`` otherwise.
:param topdown:
Please see the documentation for :func:`os.walk`
:param followlinks:
Please see the documentation for :func:`os.walk`
"""
for root, dirnames, filenames\
in walk(dir_pathname, recursive, topdown, followlinks):
for dirname in dirnames:
yield absolute_path(os.path.join(root, dirname))
for filename in filenames:
yield absolute_path(os.path.join(root, filename))
def list_directories(dir_pathname,
recursive=True,
topdown=True,
followlinks=False):
"""
Enlists all the directories using their absolute paths within the specified
directory, optionally recursively.
:param dir_pathname:
The directory to traverse.
:param recursive:
``True`` for walking recursively through the directory tree;
``False`` otherwise.
:param topdown:
Please see the documentation for :func:`os.walk`
:param followlinks:
Please see the documentation for :func:`os.walk`
"""
for root, dirnames, filenames\
in walk(dir_pathname, recursive, topdown, followlinks):
for dirname in dirnames:
yield absolute_path(os.path.join(root, dirname))
def list_files(dir_pathname,
recursive=True,
topdown=True,
followlinks=False):
"""
Enlists all the files using their absolute paths within the specified
directory, optionally recursively.
:param dir_pathname:
The directory to traverse.
:param recursive:
``True`` for walking recursively through the directory tree;
``False`` otherwise.
:param topdown:
Please see the documentation for :func:`os.walk`
:param followlinks:
Please see the documentation for :func:`os.walk`
"""
for root, dirnames, filenames\
in walk(dir_pathname, recursive, topdown, followlinks):
for filename in filenames:
yield absolute_path(os.path.join(root, filename))
def absolute_path(path):
"""
Returns the absolute path for the given path and normalizes the path.
:param path:
Path for which the absolute normalized path will be found.
:returns:
Absolute normalized path.
"""
return os.path.abspath(os.path.normpath(path))
def real_absolute_path(path):
"""
Returns the real absolute normalized path for the given path.
:param path:
Path for which the real absolute normalized path will be found.
:returns:
Real absolute normalized path.
"""
return os.path.realpath(absolute_path(path))
def parent_dir_path(path):
"""
Returns the parent directory path.
:param path:
Path for which the parent directory will be obtained.
:returns:
Parent directory path.
"""
return absolute_path(os.path.dirname(path))

View file

@ -0,0 +1,265 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# patterns.py: Common wildcard searching/filtering functionality for files.
#
# Copyright (C) 2010 Yesudeep Mangalapilly <yesudeep@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
:module: pathtools.patterns
:synopsis: Wildcard pattern matching and filtering functions for paths.
:author: Yesudeep Mangalapilly <yesudeep@gmail.com>
Functions
---------
.. autofunction:: match_path
.. autofunction:: match_path_against
.. autofunction:: filter_paths
"""
from fnmatch import fnmatch, fnmatchcase
__all__ = ['match_path',
'match_path_against',
'match_any_paths',
'filter_paths']
def _string_lower(s):
"""
Convenience function to lowercase a string (the :mod:`string` module is
deprecated/removed in Python 3.0).
:param s:
The string which will be lowercased.
:returns:
Lowercased copy of string s.
"""
return s.lower()
def match_path_against(pathname, patterns, case_sensitive=True):
"""
Determines whether the pathname matches any of the given wildcard patterns,
optionally ignoring the case of the pathname and patterns.
:param pathname:
A path name that will be matched against a wildcard pattern.
:param patterns:
A list of wildcard patterns to match_path the filename against.
:param case_sensitive:
``True`` if the matching should be case-sensitive; ``False`` otherwise.
:returns:
``True`` if the pattern matches; ``False`` otherwise.
Doctests::
>>> match_path_against("/home/username/foobar/blah.py", ["*.py", "*.txt"], False)
True
>>> match_path_against("/home/username/foobar/blah.py", ["*.PY", "*.txt"], True)
False
>>> match_path_against("/home/username/foobar/blah.py", ["*.PY", "*.txt"], False)
True
>>> match_path_against("C:\\windows\\blah\\BLAH.PY", ["*.py", "*.txt"], True)
False
>>> match_path_against("C:\\windows\\blah\\BLAH.PY", ["*.py", "*.txt"], False)
True
"""
if case_sensitive:
match_func = fnmatchcase
pattern_transform_func = (lambda w: w)
else:
match_func = fnmatch
pathname = pathname.lower()
pattern_transform_func = _string_lower
for pattern in set(patterns):
pattern = pattern_transform_func(pattern)
if match_func(pathname, pattern):
return True
return False
def _match_path(pathname,
included_patterns,
excluded_patterns,
case_sensitive=True):
"""Internal function same as :func:`match_path` but does not check arguments.
Doctests::
>>> _match_path("/users/gorakhargosh/foobar.py", ["*.py"], ["*.PY"], True)
True
>>> _match_path("/users/gorakhargosh/FOOBAR.PY", ["*.py"], ["*.PY"], True)
False
>>> _match_path("/users/gorakhargosh/foobar/", ["*.py"], ["*.txt"], False)
False
>>> _match_path("/users/gorakhargosh/FOOBAR.PY", ["*.py"], ["*.PY"], False)
Traceback (most recent call last):
...
ValueError: conflicting patterns `set(['*.py'])` included and excluded
"""
if not case_sensitive:
included_patterns = set(map(_string_lower, included_patterns))
excluded_patterns = set(map(_string_lower, excluded_patterns))
else:
included_patterns = set(included_patterns)
excluded_patterns = set(excluded_patterns)
common_patterns = included_patterns & excluded_patterns
if common_patterns:
raise ValueError('conflicting patterns `%s` included and excluded'\
% common_patterns)
return (match_path_against(pathname, included_patterns, case_sensitive)\
and not match_path_against(pathname, excluded_patterns,
case_sensitive))
def match_path(pathname,
included_patterns=None,
excluded_patterns=None,
case_sensitive=True):
"""
Matches a pathname against a set of acceptable and ignored patterns.
:param pathname:
A pathname which will be matched against a pattern.
:param included_patterns:
Allow filenames matching wildcard patterns specified in this list.
If no pattern is specified, the function treats the pathname as
a match_path.
:param excluded_patterns:
Ignores filenames matching wildcard patterns specified in this list.
If no pattern is specified, the function treats the pathname as
a match_path.
:param case_sensitive:
``True`` if matching should be case-sensitive; ``False`` otherwise.
:returns:
``True`` if the pathname matches; ``False`` otherwise.
:raises:
ValueError if included patterns and excluded patterns contain the
same pattern.
Doctests::
>>> match_path("/Users/gorakhargosh/foobar.py")
True
>>> match_path("/Users/gorakhargosh/foobar.py", case_sensitive=False)
True
>>> match_path("/users/gorakhargosh/foobar.py", ["*.py"], ["*.PY"], True)
True
>>> match_path("/users/gorakhargosh/FOOBAR.PY", ["*.py"], ["*.PY"], True)
False
>>> match_path("/users/gorakhargosh/foobar/", ["*.py"], ["*.txt"], False)
False
>>> match_path("/users/gorakhargosh/FOOBAR.PY", ["*.py"], ["*.PY"], False)
Traceback (most recent call last):
...
ValueError: conflicting patterns `set(['*.py'])` included and excluded
"""
included = ["*"] if included_patterns is None else included_patterns
excluded = [] if excluded_patterns is None else excluded_patterns
return _match_path(pathname, included, excluded, case_sensitive)
def filter_paths(pathnames,
included_patterns=None,
excluded_patterns=None,
case_sensitive=True):
"""
Filters from a set of paths based on acceptable patterns and
ignorable patterns.
:param pathnames:
A list of path names that will be filtered based on matching and
ignored patterns.
:param included_patterns:
Allow filenames matching wildcard patterns specified in this list.
If no pattern list is specified, ["*"] is used as the default pattern,
which matches all files.
:param excluded_patterns:
Ignores filenames matching wildcard patterns specified in this list.
If no pattern list is specified, no files are ignored.
:param case_sensitive:
``True`` if matching should be case-sensitive; ``False`` otherwise.
:returns:
A list of pathnames that matched the allowable patterns and passed
through the ignored patterns.
Doctests::
>>> pathnames = set(["/users/gorakhargosh/foobar.py", "/var/cache/pdnsd.status", "/etc/pdnsd.conf", "/usr/local/bin/python"])
>>> set(filter_paths(pathnames)) == pathnames
True
>>> set(filter_paths(pathnames, case_sensitive=False)) == pathnames
True
>>> set(filter_paths(pathnames, ["*.py", "*.conf"], ["*.status"], case_sensitive=True)) == set(["/users/gorakhargosh/foobar.py", "/etc/pdnsd.conf"])
True
"""
included = ["*"] if included_patterns is None else included_patterns
excluded = [] if excluded_patterns is None else excluded_patterns
for pathname in pathnames:
# We don't call the public match_path because it checks arguments
# and sets default values if none are found. We're already doing that
# above.
if _match_path(pathname, included, excluded, case_sensitive):
yield pathname
def match_any_paths(pathnames,
included_patterns=None,
excluded_patterns=None,
case_sensitive=True):
"""
Matches from a set of paths based on acceptable patterns and
ignorable patterns.
:param pathnames:
A list of path names that will be filtered based on matching and
ignored patterns.
:param included_patterns:
Allow filenames matching wildcard patterns specified in this list.
If no pattern list is specified, ["*"] is used as the default pattern,
which matches all files.
:param excluded_patterns:
Ignores filenames matching wildcard patterns specified in this list.
If no pattern list is specified, no files are ignored.
:param case_sensitive:
``True`` if matching should be case-sensitive; ``False`` otherwise.
:returns:
``True`` if any of the paths matches; ``False`` otherwise.
Doctests::
>>> pathnames = set(["/users/gorakhargosh/foobar.py", "/var/cache/pdnsd.status", "/etc/pdnsd.conf", "/usr/local/bin/python"])
>>> match_any_paths(pathnames)
True
>>> match_any_paths(pathnames, case_sensitive=False)
True
>>> match_any_paths(pathnames, ["*.py", "*.conf"], ["*.status"], case_sensitive=True)
True
>>> match_any_paths(pathnames, ["*.txt"], case_sensitive=False)
False
>>> match_any_paths(pathnames, ["*.txt"], case_sensitive=True)
False
"""
included = ["*"] if included_patterns is None else included_patterns
excluded = [] if excluded_patterns is None else excluded_patterns
for pathname in pathnames:
# We don't call the public match_path because it checks arguments
# and sets default values if none are found. We're already doing that
# above.
if _match_path(pathname, included, excluded, case_sensitive):
return True
return False

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# version.py: Version information.
# Copyright (C) 2010 Yesudeep Mangalapilly <yesudeep@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# When updating this version number, please update the
# ``docs/source/global.rst.inc`` file as well.
VERSION_MAJOR = 0
VERSION_MINOR = 1
VERSION_BUILD = 1
VERSION_INFO = (VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD)
VERSION_STRING = "%d.%d.%d" % VERSION_INFO
__version__ = VERSION_INFO

View file

@ -1,76 +0,0 @@
# -*- coding: utf-8 -*-
###############################################################################
from cPickle import dumps, loads
from xbmcgui import Window
from xbmc import log, LOGDEBUG
###############################################################################
WINDOW = Window(10000)
PREFIX = 'PLEX.%s: ' % __name__
###############################################################################
def try_encode(input_str, encoding='utf-8'):
"""
Will try to encode input_str (in unicode) to encoding. This possibly
fails with e.g. Android TV's Python, which does not accept arguments for
string.encode()
COPY to avoid importing utils on calling default.py
"""
if isinstance(input_str, str):
# already encoded
return input_str
try:
input_str = input_str.encode(encoding, "ignore")
except TypeError:
input_str = input_str.encode()
return input_str
def pickl_window(property, value=None, clear=False):
"""
Get or set window property - thread safe! For use with Pickle
Property and value must be string
"""
if clear:
WINDOW.clearProperty(property)
elif value is not None:
WINDOW.setProperty(property, value)
else:
return try_encode(WINDOW.getProperty(property))
def pickle_me(obj, window_var='plex_result'):
"""
Pickles the obj to the window variable. Use to transfer Python
objects between different PKC python instances (e.g. if default.py is
called and you'd want to use the service.py instance)
obj can be pretty much any Python object. However, classes and
functions won't work. See the Pickle documentation
"""
log('%sStart pickling' % PREFIX, level=LOGDEBUG)
pickl_window(window_var, value=dumps(obj))
log('%sSuccessfully pickled' % PREFIX, level=LOGDEBUG)
def unpickle_me(window_var='plex_result'):
"""
Unpickles a Python object from the window variable window_var.
Will then clear the window variable!
"""
result = pickl_window(window_var)
pickl_window(window_var, clear=True)
log('%sStart unpickling' % PREFIX, level=LOGDEBUG)
obj = loads(result)
log('%sSuccessfully unpickled' % PREFIX, level=LOGDEBUG)
return obj
class Playback_Successful(object):
"""
Used to communicate with another PKC Python instance
"""
listitem = None

View file

@ -1,44 +1,39 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Used to kick off Kodi playback
"""
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from threading import Thread
from os.path import join
import datetime
from xbmc import Player, sleep
import xbmc
from PlexAPI import API
from PlexFunctions import GetPlexMetadata, init_plex_playqueue
from downloadutils import DownloadUtils as DU
import plexdb_functions as plexdb
import kodidb_functions as kodidb
import playlist_func as PL
import playqueue as PQ
from playutils import PlayUtils
from PKC_listitem import PKC_ListItem
from pickler import pickle_me, Playback_Successful
import json_rpc as js
from utils import settings, dialog, language as lang, try_encode
from plexbmchelper.subscribers import LOCKER
import variables as v
import state
from .plex_api import API
from .plex_db import PlexDB
from .kodi_db import KodiVideoDB
from . import plex_functions as PF, playlist_func as PL, playqueue as PQ
from . import json_rpc as js, variables as v, utils, transfer
from . import playback_decision, app
from . import exceptions
###############################################################################
LOG = getLogger("PLEX." + __name__)
LOG = getLogger('PLEX.playback')
# Do we need to return ultimately with a setResolvedUrl?
RESOLVE = True
# We're "failing" playback with a video of 0 length
NULL_VIDEO = join(v.ADDON_FOLDER, 'addons', v.ADDON_ID, 'empty_video.mp4')
TRY_TO_SEEK_FOR = 300 # =30 seconds
IGNORE_SECONDS_AT_START = 15
###############################################################################
@LOCKER.lockthis
def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True,
resume=False):
"""
Hit this function for addon path playback, Plex trailers, etc.
Will setup playback first, then on second call complete playback.
Will set Playback_Successful() with potentially a PKC_ListItem() attached
Will set Playback_Successful() with potentially a PKCListItem() attached
(to be consumed by setResolvedURL in default.py)
If trailers or additional (movie-)parts are added, default.py is released
@ -49,56 +44,156 @@ 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
service.py Python instance
"""
LOG.info('playback_triage called with plex_id %s, plex_type %s, path %s, '
'resolve %s,', plex_id, plex_type, path, resolve)
try:
_playback_triage(plex_id, plex_type, path, resolve, resume)
finally:
# Reset some playback variables the user potentially set to init
# playback
app.PLAYSTATE.context_menu_play = False
app.PLAYSTATE.force_transcode = False
def _playback_triage(plex_id, plex_type, path, resolve, resume):
plex_id = utils.cast(int, plex_id)
LOG.debug('playback_triage called with plex_id %s, plex_type %s, path %s, '
'resolve %s, resume %s', plex_id, plex_type, path, resolve, resume)
global RESOLVE
# If started via Kodi context menu, we never resolve
RESOLVE = resolve if not state.CONTEXT_MENU_PLAY else False
if not state.AUTHENTICATED:
LOG.error('Not yet authenticated for PMS, abort starting playback')
# "Unauthorized for PMS"
dialog('notification', lang(29999), lang(30017))
RESOLVE = resolve if not app.PLAYSTATE.context_menu_play else False
if not app.CONN.online or not app.ACCOUNT.authenticated:
if not app.CONN.online:
LOG.error('PMS not online for playback')
# "{0} offline"
utils.dialog('notification',
utils.lang(29999),
utils.lang(39213).format(app.CONN.server_name),
icon='{plex}')
else:
LOG.error('Not yet authenticated for PMS, abort starting playback')
# "Unauthorized for PMS"
utils.dialog('notification', utils.lang(29999), utils.lang(30017))
_ensure_resolve(abort=True)
return
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type])
pos = js.get_position(playqueue.playlistid)
# Can return -1 (as in "no playlist")
pos = pos if pos != -1 else 0
LOG.debug('playQueue position %s for %s', pos, playqueue)
# Have we already initiated playback?
try:
item = playqueue.items[pos]
except IndexError:
initiate = True
else:
initiate = True if item.plex_id != plex_id else False
if initiate:
_playback_init(plex_id, plex_type, playqueue, pos)
else:
# kick off playback on second pass
_conclude_playback(playqueue, pos)
with app.APP.lock_playqueues:
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type])
try:
pos = js.get_position(playqueue.playlistid)
except KeyError:
# Kodi bug - Playlist plays (not Playqueue) will ALWAYS be audio for
# add-on paths
LOG.debug('No position returned from player! Assuming playlist')
playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_AUDIO)
try:
pos = js.get_position(playqueue.playlistid)
except KeyError:
LOG.debug('Assuming video instead of audio playlist playback')
playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_VIDEO)
try:
pos = js.get_position(playqueue.playlistid)
except KeyError:
LOG.error('Still no position - abort')
# "Play error"
utils.dialog('notification',
utils.lang(29999),
utils.lang(30128),
icon='{error}')
_ensure_resolve(abort=True)
return
# HACK to detect playback of playlists for add-on paths
items = js.playlist_get_items(playqueue.playlistid)
try:
item = items[pos]
except IndexError:
LOG.debug('Could not apply playlist hack! Probably Widget playback')
else:
if ('id' not in item and
item.get('type') == 'unknown' and item.get('title') == ''):
LOG.debug('Kodi playlist play detected')
_playlist_playback(plex_id)
return
# Can return -1 (as in "no playlist")
pos = pos if pos != -1 else 0
LOG.debug('playQueue position %s for %s', pos, playqueue)
# Have we already initiated playback?
try:
item = playqueue.items[pos]
except IndexError:
LOG.debug('PKC playqueue yet empty, need to initialize playback')
initiate = True
else:
if item.plex_id != plex_id:
LOG.debug('Received new plex_id%s, expected %s',
plex_id, item.plex_id)
initiate = True
else:
initiate = False
if initiate:
_playback_init(plex_id, plex_type, playqueue, pos, resume)
else:
# kick off playback on second pass, resume was already set on first
# pass (threaded_playback will seek to resume)
_conclude_playback(playqueue, pos)
def _playback_init(plex_id, plex_type, playqueue, pos):
def _playlist_playback(plex_id):
"""
Really annoying Kodi behavior: Kodi will throw the ENTIRE playlist some-
where, causing Playlist.onAdd to fire for each item like this:
Playlist.OnAdd Data: {u'item': {u'type': u'episode', u'id': 164},
u'playlistid': 0,
u'position': 2}
This does NOT work for Addon paths, type and id will be unknown:
{u'item': {u'type': u'unknown'},
u'playlistid': 0,
u'position': 7}
At the end, only the element being played actually shows up in the Kodi
playqueue.
Hence: if we fail the first addon paths call, Kodi will start playback
for the next item in line :-)
(by the way: trying to get active Kodi player id will return [])
"""
xml = PF.GetPlexMetadata(plex_id, reraise=True)
if xml in (None, 401):
_ensure_resolve(abort=True)
return
# Kodi bug: playqueue will ALWAYS be audio playqueue UNTIL playback
# has actually started. Need to tell Kodimonitor
playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_AUDIO)
playqueue.clear(kodi=False)
# Set the flag for the potentially WRONG audio playlist so Kodimonitor
# can pick up on it
playqueue.kodi_playlist_playback = True
playlist_item = PL.playlist_item_from_xml(xml[0])
playqueue.items.append(playlist_item)
_conclude_playback(playqueue, pos=0)
def _playback_init(plex_id, plex_type, playqueue, pos, resume):
"""
Playback setup if Kodi starts playing an item for the first time.
"""
LOG.info('Initializing PKC playback')
xml = GetPlexMetadata(plex_id)
try:
xml[0].attrib
except (IndexError, TypeError, AttributeError):
LOG.debug('Initializing PKC playback')
# Stop playback so we don't get an error message that the last item of the
# queue failed to play
app.APP.player.stop()
xml = PF.GetPlexMetadata(plex_id, reraise=True)
if xml in (None, 401):
LOG.error('Could not get a PMS xml for plex id %s', plex_id)
# "Play error"
dialog('notification', lang(29999), lang(30128), icon='{error}')
_ensure_resolve(abort=True)
return
if playqueue.kodi_pl.size() > 1:
if (xbmc.getCondVisibility('Window.IsVisible(Home.xml)') and
plex_type in v.PLEX_VIDEOTYPES and
playqueue.kodi_pl.size() > 1):
# playqueue.kodi_pl.size() could return more than one - since playback
# was initiated from the audio queue!
LOG.debug('Detected widget playback for videos')
elif playqueue.kodi_pl.size() > 1:
# Special case - we already got a filled Kodi playqueue
try:
_init_existing_kodi_playlist(playqueue, pos)
except PL.PlaylistError:
except exceptions.PlaylistError:
LOG.error('Playback_init for existing Kodi playlist failed')
_ensure_resolve(abort=True)
return
@ -108,60 +203,59 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
return
# "Usual" case - consider trailers and parts and build both Kodi and Plex
# playqueues
# Pass dummy PKC video with 0 length so Kodi immediately stops playback
# and we can build our own playqueue.
# Release default.py
_ensure_resolve()
api = API(xml[0])
if (app.PLAYSTATE.context_menu_play and
api.resume_point() and
api.plex_type in v.PLEX_VIDEOTYPES):
# User chose to either play via PMS or to force transcode
# Need to prompt whether we should resume_playback
resume = resume_dialog(int(api.resume_point()))
if resume is None:
# User cancelled dialog
return
LOG.debug('Using resume %s', resume)
resume = resume or False
trailers = False
if (plex_type == v.PLEX_TYPE_MOVIE and not api.resume_point() and
settings('enableCinema') == "true"):
if settings('askCinema') == "true":
if (not resume and plex_type == v.PLEX_TYPE_MOVIE and
utils.settings('enableCinema') == "true"):
if utils.settings('askCinema') == "true":
# "Play trailers?"
trailers = dialog('yesno', lang(29999), lang(33016))
trailers = True if trailers else False
trailers = utils.yesno_dialog(utils.lang(29999), utils.lang(33016))
else:
trailers = True
LOG.debug('Playing trailers: %s', trailers)
if RESOLVE:
# Sleep a bit to let setResolvedUrl do its thing - bit ugly
sleep_timer = 0
while not state.PKC_CAUSED_STOP_DONE:
sleep(50)
sleep_timer += 1
if sleep_timer > 100:
break
LOG.debug('Resuming: %s. Playing trailers: %s', resume, trailers)
playqueue.clear()
if plex_type != v.PLEX_TYPE_CLIP:
# Post to the PMS to create a playqueue - in any case due to Companion
xml = init_plex_playqueue(plex_id,
xml.attrib.get('librarySectionUUID'),
mediatype=plex_type,
trailers=trailers)
xml = PF.init_plex_playqueue(plex_id,
plex_type,
xml.get('librarySectionUUID'),
trailers=trailers)
if xml is None:
LOG.error('Could not get a playqueue xml for plex id %s, UUID %s',
plex_id, xml.attrib.get('librarySectionUUID'))
LOG.error('Could not get a playqueue xml for plex id %s', plex_id)
# "Play error"
dialog('notification', lang(29999), lang(30128), icon='{error}')
utils.dialog('notification',
utils.lang(29999),
utils.lang(30128),
icon='{error}')
# Do NOT use _ensure_resolve() because we resolved above already
state.CONTEXT_MENU_PLAY = False
state.FORCE_TRANSCODE = False
state.RESUME_PLAYBACK = False
return
PL.get_playlist_details_from_xml(playqueue, xml)
stack = _prep_playlist_stack(xml)
stack = _prep_playlist_stack(xml, resume)
_process_stack(playqueue, stack)
# Always resume if playback initiated via PMS and there IS a resume
# point
offset = api.resume_point() * 1000 if state.CONTEXT_MENU_PLAY else None
# Reset some playback variables
state.CONTEXT_MENU_PLAY = False
state.FORCE_TRANSCODE = False
offset = _use_kodi_db_offset(playqueue.items[pos].plex_id,
playqueue.items[pos].plex_type,
playqueue.items[pos].offset) if resume else 0
# New thread to release this one sooner (e.g. harddisk spinning up)
thread = Thread(target=threaded_playback,
args=(playqueue.kodi_pl, pos, offset))
thread.setDaemon(True)
LOG.info('Done initializing playback, starting Kodi player at pos %s and '
'resume point %s', pos, offset)
LOG.debug('Done initializing playback, starting Kodi player at pos %s and '
'offset %s', pos, offset)
# Ensure that PKC playqueue monitor ignores the changes we just made
playqueue.pkc_edit = True
# By design, PKC will start Kodi playback using Player().play(). Kodi
# caches paths like our plugin://pkc. If we use Player().play() between
# 2 consecutive startups of exactly the same Kodi library item, Kodi's
@ -169,8 +263,6 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
# plugin://pkc will be lost; Kodi will try to startup playback for an empty
# path: log entry is "CGUIWindowVideoBase::OnPlayMedia <missing path>"
thread.start()
# Ensure that PKC playqueue monitor ignores the changes we just made
playqueue.pkc_edit = True
def _ensure_resolve(abort=False):
@ -183,19 +275,33 @@ def _ensure_resolve(abort=False):
will be destroyed.
"""
if RESOLVE:
LOG.debug('Passing dummy path to Kodi')
# if not state.CONTEXT_MENU_PLAY:
# Because playback won't start with context menu play
state.PKC_CAUSED_STOP = True
state.PKC_CAUSED_STOP_DONE = False
result = Playback_Successful()
result.listitem = PKC_ListItem(path=NULL_VIDEO)
pickle_me(result)
# Releases the other Python thread without a ListItem
transfer.send(True)
# Wait for default.py to have completed xbmcplugin.setResolvedUrl()
transfer.wait_for_transfer(source='default')
if abort:
# Reset some playback variables
state.CONTEXT_MENU_PLAY = False
state.FORCE_TRANSCODE = False
state.RESUME_PLAYBACK = False
utils.dialog('notification',
heading='{plex}',
message=utils.lang(30128),
icon='{error}',
time=3000)
def resume_dialog(resume):
"""
Pass the resume [int] point in seconds. Returns True if user chose to
resume. Returns None if user cancelled
"""
# "Resume from {0:s}"
# "Start from beginning"
resume = datetime.timedelta(seconds=resume)
LOG.debug('Showing PKC resume dialog for resume: %s', resume)
answ = utils.dialog('contextmenu',
[utils.lang(12022).replace('{0:s}', '{0}').format(unicode(resume)),
utils.lang(12021)])
if answ == -1:
return
return answ == 0
def _init_existing_kodi_playlist(playqueue, pos):
@ -207,27 +313,31 @@ def _init_existing_kodi_playlist(playqueue, pos):
kodi_items = js.playlist_get_items(playqueue.playlistid)
if not kodi_items:
LOG.error('No Kodi items returned')
raise PL.PlaylistError('No Kodi items returned')
item = PL.init_Plex_playlist(playqueue, kodi_item=kodi_items[pos])
item.force_transcode = state.FORCE_TRANSCODE
raise exceptions.PlaylistError('No Kodi items returned')
item = PL.init_plex_playqueue(playqueue, kodi_item=kodi_items[pos])
item.force_transcode = app.PLAYSTATE.force_transcode
# playqueue.py will add the rest - this will likely put the PMS under
# a LOT of strain if the following Kodi setting is enabled:
# Settings -> Player -> Videos -> Play next video automatically
LOG.debug('Done init_existing_kodi_playlist')
def _prep_playlist_stack(xml):
def _prep_playlist_stack(xml, resume):
"""
resume [bool] will set the resume point of the LAST item of the stack, for
part 1 only
"""
stack = []
for item in xml:
for i, item in enumerate(xml):
api = API(item)
if (state.CONTEXT_MENU_PLAY is False and
api.plex_type() not in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_EPISODE)):
if (app.PLAYSTATE.context_menu_play is False and
api.plex_type not in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_EPISODE)):
# If user chose to play via PMS or force transcode, do not
# use the item path stored in the Kodi DB
with plexdb.Get_Plex_DB() as plex_db:
plex_dbitem = plex_db.getItem_byId(api.plex_id())
kodi_id = plex_dbitem[0] if plex_dbitem else None
kodi_type = plex_dbitem[4] if plex_dbitem else None
with PlexDB(lock=False) as plexdb:
db_item = plexdb.item_by_id(api.plex_id, api.plex_type)
kodi_id = db_item['kodi_id'] if db_item else None
kodi_type = db_item['kodi_type'] if db_item else None
else:
# We will never store clips (trailers) in the Kodi DB.
# Also set kodi_id to None for playback via PMS, so that we're
@ -238,15 +348,20 @@ def _prep_playlist_stack(xml):
kodi_id = None
kodi_type = None
for part, _ in enumerate(item[0]):
api.set_part_number(part)
api.part = part
if kodi_id is None:
# Need to redirect again to PKC to conclude playback
path = ('plugin://%s/?plex_id=%s&plex_type=%s&mode=play'
% (v.ADDON_TYPE[api.plex_type()],
api.plex_id(),
api.plex_type()))
listitem = api.create_listitem()
listitem.setPath(try_encode(path))
path = api.fullpath(force_addon=True)[0]
# Using different paths than the ones saved in the Kodi DB
# fixes Kodi immediately resuming the video if one restarts
# the same video again after playback
# WARNING: This fixes startup, but renders Kodi unstable
# path = path.replace('plugin.video.plexkodiconnect.tvshows',
# 'plugin.video.plexkodiconnect', 1)
# path = path.replace('plugin.video.plexkodiconnect.movies',
# 'plugin.video.plexkodiconnect', 1)
listitem = api.listitem()
listitem.setPath(path.encode('utf-8'))
else:
# Will add directly via the Kodi DB
path = None
@ -260,6 +375,7 @@ def _prep_playlist_stack(xml):
'part': part,
'playcount': api.viewcount(),
'offset': api.resume_point(),
'resume': resume if part == 0 and i + 1 == len(xml) else None,
'id': api.item_id()
})
return stack
@ -291,10 +407,27 @@ def _process_stack(playqueue, stack):
playlist_item.offset = item['offset']
playlist_item.part = item['part']
playlist_item.id = item['id']
playlist_item.force_transcode = state.FORCE_TRANSCODE
playlist_item.force_transcode = app.PLAYSTATE.force_transcode
playlist_item.resume = item['resume']
pos += 1
def _use_kodi_db_offset(plex_id, plex_type, plex_offset):
"""
Do NOT use item.offset directly but get it from the Kodi DB (Plex might not
have gotten the last resume point)
"""
if plex_type not in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_EPISODE):
return plex_offset
with PlexDB(lock=False) as plexdb:
db_item = plexdb.item_by_id(plex_id, plex_type)
if db_item:
with KodiVideoDB(lock=False) as kodidb:
return kodidb.get_resume(db_item['kodi_fileid'])
else:
return plex_offset
def _conclude_playback(playqueue, pos):
"""
ONLY if actually being played (e.g. at 5th position of a playqueue).
@ -310,41 +443,30 @@ def _conclude_playback(playqueue, pos):
start playback
return PKC listitem attached to result
"""
LOG.info('Concluding playback for playqueue position %s', pos)
result = Playback_Successful()
listitem = PKC_ListItem()
LOG.debug('Concluding playback for playqueue position %s', pos)
item = playqueue.items[pos]
if item.xml is not None:
# Got a Plex element
api = API(item.xml)
api.set_part_number(item.part)
api.create_listitem(listitem)
playutils = PlayUtils(api, item)
playurl = playutils.getPlayUrl()
else:
playurl = item.file
listitem.setPath(try_encode(playurl))
if item.playmethod == 'DirectStream':
listitem.setSubtitles(api.cache_external_subs())
elif item.playmethod == 'Transcode':
playutils.audio_subtitle_prefs(listitem)
if state.RESUME_PLAYBACK is True:
state.RESUME_PLAYBACK = False
if (item.offset is None and
item.plex_type not in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_CLIP)):
with plexdb.Get_Plex_DB() as plex_db:
plex_dbitem = plex_db.getItem_byId(item.plex_id)
file_id = plex_dbitem[1] if plex_dbitem else None
with kodidb.GetKodiDB('video') as kodi_db:
item.offset = kodi_db.get_resume(file_id)
LOG.info('Resuming playback at %s', item.offset)
listitem.setProperty('StartOffset', str(item.offset))
listitem.setProperty('resumetime', str(item.offset))
# Reset the resumable flag
result.listitem = listitem
pickle_me(result)
LOG.info('Done concluding playback')
if item.api.mediastream_number() is None:
# E.g. user could choose between several media streams and cancelled
LOG.debug('Did not get a mediastream_number')
_ensure_resolve()
return
item.api.part = item.part or 0
playback_decision.set_pkc_playmethod(item.api, item)
if not playback_decision.audio_subtitle_prefs(item.api, item):
LOG.info('Did not set audio subtitle prefs, aborting silently')
_ensure_resolve()
return
playback_decision.set_playurl(item.api, item)
if not item.file:
LOG.info('Did not get a playurl, aborting playback silently')
_ensure_resolve()
return
listitem = item.api.listitem(listitem=transfer.PKCListItem, resume=False)
listitem.setPath(item.file.encode('utf-8'))
if item.playmethod != v.PLAYBACK_METHOD_DIRECT_PATH:
listitem.setSubtitles(item.api.cache_external_subs())
transfer.send(listitem)
LOG.debug('Done concluding playback')
def process_indirect(key, offset, resolve=True):
@ -358,59 +480,65 @@ def process_indirect(key, offset, resolve=True):
Set resolve to False if playback should be kicked off directly, not via
setResolvedUrl
"""
LOG.info('process_indirect called with key: %s, offset: %s, resolve: %s',
key, offset, resolve)
LOG.debug('process_indirect called with key: %s, offset: %s, resolve: %s',
key, offset, resolve)
global RESOLVE
RESOLVE = resolve
result = Playback_Successful()
offset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(offset)) if offset != '0' else None
if key.startswith('http') or key.startswith('{server}'):
xml = DU().downloadUrl(key)
xml = PF.get_playback_xml(key, app.CONN.server_name)
elif key.startswith('/system/services'):
xml = DU().downloadUrl('http://node.plexapp.com:32400%s' % key)
xml = PF.get_playback_xml('http://node.plexapp.com:32400%s' % key,
'plexapp.com',
authenticate=False,
token=app.ACCOUNT.plex_token)
else:
xml = DU().downloadUrl('{server}%s' % key)
try:
xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error('Could not download PMS metadata')
xml = PF.get_playback_xml('{server}%s' % key, app.CONN.server_name)
if xml is None:
_ensure_resolve(abort=True)
return
if offset != '0':
offset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(offset))
# Todo: implement offset
api = API(xml[0])
listitem = PKC_ListItem()
api.create_listitem(listitem)
listitem = api.listitem(listitem=transfer.PKCListItem, resume=False)
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()])
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type])
playqueue.clear()
item = PL.Playlist_Item()
item.xml = xml[0]
item.offset = int(offset)
item.plex_type = v.PLEX_TYPE_CLIP
item.playmethod = 'DirectStream'
item = PL.playlist_item_from_xml(xml[0])
item.offset = offset
item.playmethod = v.PLAYBACK_METHOD_DIRECT_PLAY
# Need to get yet another xml to get the final playback url
xml = DU().downloadUrl('http://node.plexapp.com:32400%s'
% xml[0][0][0].attrib['key'])
try:
xml[0].attrib
xml = PF.get_playback_xml('http://node.plexapp.com:32400%s'
% xml[0][0][0].attrib['key'],
'plexapp.com',
authenticate=False,
token=app.ACCOUNT.plex_token)
except (TypeError, IndexError, AttributeError):
LOG.error('Could not download last xml for playurl')
LOG.error('XML malformed: %s', xml.attrib)
xml = None
if xml is None:
_ensure_resolve(abort=True)
return
playurl = xml[0].attrib['key']
try:
playurl = xml[0].attrib['key']
except (TypeError, IndexError, AttributeError):
LOG.error('Last xml malformed: %s', xml.attrib)
_ensure_resolve(abort=True)
return
item.file = playurl
listitem.setPath(try_encode(playurl))
listitem.setPath(utils.try_encode(playurl))
playqueue.items.append(item)
if resolve is True:
result.listitem = listitem
pickle_me(result)
transfer.send(listitem)
else:
thread = Thread(target=Player().play,
args={'item': try_encode(playurl),
thread = Thread(target=app.APP.player.play,
args={'item': utils.try_encode(playurl),
'listitem': listitem})
thread.setDaemon(True)
LOG.info('Done initializing PKC playback, starting Kodi player')
LOG.debug('Done initializing PKC playback, starting Kodi player')
thread.start()
@ -421,41 +549,67 @@ def play_xml(playqueue, xml, offset=None, start_plex_id=None):
Either supply the ratingKey of the starting Plex element. Or set
playqueue.selectedItemID
"""
LOG.info("play_xml called with offset %s, start_plex_id %s",
offset, start_plex_id)
stack = _prep_playlist_stack(xml)
offset = int(offset) / 1000 if offset else None
LOG.debug("play_xml called with offset %s, start_plex_id %s",
offset, start_plex_id)
start_item = start_plex_id if start_plex_id is not None \
else playqueue.selectedItemID
for startpos, video in enumerate(xml):
api = API(video)
if api.plex_id == start_item:
break
else:
startpos = 0
stack = _prep_playlist_stack(xml, resume=False)
if offset:
stack[startpos]['resume'] = True
_process_stack(playqueue, stack)
LOG.debug('Playqueue after play_xml update: %s', playqueue)
if start_plex_id is not None:
for startpos, item in enumerate(playqueue.items):
if item.plex_id == start_plex_id:
break
else:
startpos = 0
else:
for startpos, item in enumerate(playqueue.items):
if item.id == playqueue.selectedItemID:
break
else:
startpos = 0
thread = Thread(target=threaded_playback,
args=(playqueue.kodi_pl, startpos, offset))
LOG.info('Done play_xml, starting Kodi player at position %s', startpos)
LOG.debug('Done play_xml, starting Kodi player at position %s', startpos)
thread.start()
def threaded_playback(kodi_playlist, startpos, offset):
"""
Seek immediately after kicking off playback is not reliable.
Seek immediately after kicking off playback is not reliable. We even seek
to 0 (starting position) in case Kodi wants to resume but we want to start
over.
offset: resume position in seconds [int/float]
"""
player = Player()
player.play(kodi_playlist, None, False, startpos)
if offset and offset != '0':
i = 0
while not player.isPlaying():
sleep(100)
i += 1
if i > 100:
LOG.error('Could not seek to %s', offset)
return
js.seek_to(int(offset))
LOG.debug('threaded_playback with startpos %s, offset %s',
startpos, offset)
app.APP.player.play(kodi_playlist, None, False, startpos)
offset = offset if offset else 0
i = 0
while not app.APP.is_playing or not js.get_player_ids():
if app.APP.monitor.waitForAbort(0.1):
# PKC needs to quit
return
i += 1
if i > TRY_TO_SEEK_FOR:
LOG.error('Could not seek to %s', offset)
return
try:
if offset == 0 and app.APP.player.getTime() < IGNORE_SECONDS_AT_START:
LOG.debug('Avoiding small jump to the very start of the video')
return
except RuntimeError:
# RuntimeError: XBMC is not playing any media file
pass
i = 0
answ = js.seek_to(offset * 1000)
while 'error' in answ:
# Kodi sometimes returns {u'message': u'Failed to execute method.',
# u'code': -32100} if user quickly switches videos
if app.APP.monitor.waitForAbort(0.1):
# PKC needs to quit
return
i += 1
if i > TRY_TO_SEEK_FOR:
LOG.error('Failed to seek to %s. Error: %s', offset, answ)
return
answ = js.seek_to(offset * 1000)
LOG.debug('Seek to offset %s successful', offset)

View file

@ -0,0 +1,447 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from requests import exceptions
from .downloadutils import DownloadUtils as DU
from .plex_api import API
from . import plex_functions as PF, utils, variables as v
LOG = getLogger('PLEX.playback_decision')
# largest signed 32bit integer: 2147483
MAX_SIGNED_INT = int(2**31 - 1)
# PMS answer codes
DIRECT_PLAY_OK = 1000
CONVERSION_OK = 1001 # PMS can either direct stream or transcode
def set_pkc_playmethod(api, item):
item.playmethod = int(utils.settings('playType'))
LOG.info('User chose playback method %s in PKC settings',
v.EXPLICIT_PLAYBACK_METHOD[item.playmethod])
_initial_best_playback_method(api, item)
LOG.info('PKC decided on playback method %s',
v.EXPLICIT_PLAYBACK_METHOD[item.playmethod])
def set_playurl(api, item):
try:
if item.playmethod == v.PLAYBACK_METHOD_DIRECT_PATH:
# No need to ask the PMS whether we can play - we circumvent
# the PMS entirely
return
LOG.info('Lets ask the PMS next')
try:
_pms_playback_decision(api, item)
except (exceptions.RequestException,
AttributeError,
IndexError,
SystemExit):
LOG.warn('Could not find suitable settings for playback, aborting')
utils.ERROR(notify=True)
item.playmethod = None
item.file = None
else:
item.file = api.transcode_video_path(item.playmethod,
quality=item.quality)
finally:
LOG.info('The playurl for %s is: %s',
v.EXPLICIT_PLAYBACK_METHOD[item.playmethod], item.file)
def _initial_best_playback_method(api, item):
"""
Sets the highest available playback method without talking to the PMS
Also sets self.path for a direct path, if available and accessible
"""
item.file = api.file_path()
item.file = api.validate_playurl(item.file, api.plex_type, force_check=True)
# Check whether we have a strm file that we need to throw at Kodi 1:1
if item.file is not None and item.file.endswith('.strm'):
# Use direct path in any case, regardless of user setting
LOG.debug('.strm file detected')
item.playmethod = v.PLAYBACK_METHOD_DIRECT_PATH
elif _must_transcode(api, item):
item.playmethod = v.PLAYBACK_METHOD_TRANSCODE
elif item.playmethod in (v.PLAYBACK_METHOD_DIRECT_PLAY,
v.PLAYBACK_METHOD_DIRECT_STREAM):
pass
elif item.file is None:
# E.g. direct path was not possible to access
item.playmethod = v.PLAYBACK_METHOD_DIRECT_PLAY
else:
item.playmethod = v.PLAYBACK_METHOD_DIRECT_PATH
def _pms_playback_decision(api, item):
"""
We CANNOT distinguish direct playing from direct streaming from the PMS'
answer
"""
ask_for_user_quality_settings = False
if item.playmethod <= 2:
LOG.info('Asking PMS with maximal quality settings')
item.quality = _max_quality()
decision_api = _ask_pms(api, item)
if decision_api.decision_code() > CONVERSION_OK:
ask_for_user_quality_settings = True
else:
ask_for_user_quality_settings = True
if ask_for_user_quality_settings:
item.quality = _transcode_quality()
LOG.info('Asking PMS with user quality settings')
decision_api = _ask_pms(api, item)
# Process the PMS answer
if decision_api.decision_code() > CONVERSION_OK:
LOG.error('Neither DirectPlay, DirectStream nor transcoding possible')
error = '%s\n%s' % (decision_api.general_play_decision_text(),
decision_api.transcode_decision_text())
utils.messageDialog(heading=utils.lang(29999),
msg=error)
raise AttributeError('Neither DirectPlay, DirectStream nor transcoding possible')
if (item.playmethod == v.PLAYBACK_METHOD_DIRECT_PLAY and
decision_api.decision_code() == DIRECT_PLAY_OK):
# All good
return
LOG.info('PMS video stream decision: %s, PMS audio stream decision: %s, '
'PMS subtitle stream decision: %s',
decision_api.video_decision(),
decision_api.audio_decision(),
decision_api.subtitle_decision())
# Only look at the video stream since that'll be most CPU-intensive for
# the PMS
video_direct_streaming = decision_api.video_decision() == 'copy'
if video_direct_streaming:
if item.playmethod < v.PLAYBACK_METHOD_DIRECT_STREAM:
LOG.warn('The PMS forces us to direct stream')
# "PMS enforced direct streaming"
utils.dialog('notification',
utils.lang(29999),
utils.lang(33005),
icon='{plex}')
item.playmethod = v.PLAYBACK_METHOD_DIRECT_STREAM
else:
if item.playmethod < v.PLAYBACK_METHOD_TRANSCODE:
LOG.warn('The PMS forces us to transcode')
# "PMS enforced transcoding"
utils.dialog('notification',
utils.lang(29999),
utils.lang(33004),
icon='{plex}')
item.playmethod = v.PLAYBACK_METHOD_TRANSCODE
def _ask_pms(api, item):
xml = PF.playback_decision(path=api.path_and_plex_id(),
media=api.mediastream,
part=api.part,
playmethod=item.playmethod,
video=api.plex_type in v.PLEX_VIDEOTYPES,
args=item.quality)
decision_api = API(xml)
LOG.info('PMS general decision %s: %s',
decision_api.general_play_decision_code(),
decision_api.general_play_decision_text())
LOG.info('PMS Direct Play decision %s: %s',
decision_api.direct_play_decision_code(),
decision_api.direct_play_decision_text())
LOG.info('PMS MDE decision %s: %s',
decision_api.mde_play_decision_code(),
decision_api.mde_play_decision_text())
LOG.info('PMS transcoding decision %s: %s',
decision_api.transcode_decision_code(),
decision_api.transcode_decision_text())
return decision_api
def _must_transcode(api, item):
"""
Returns True if we need to transcode because
- codec is in h265
- 10bit video codec
- HEVC codec
- playqueue_item force_transcode is set to True
- state variable FORCE_TRANSCODE set to True
(excepting trailers etc.)
- video bitrate above specified settings bitrate
if the corresponding file settings are set to 'true'
"""
if api.plex_type in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_SONG):
LOG.info('Plex clip or music track, not transcoding')
return False
if item.playmethod == v.PLAYBACK_METHOD_TRANSCODE:
return True
videoCodec = api.video_codec()
LOG.debug("videoCodec received from the PMS: %s", videoCodec)
if item.force_transcode is True:
LOG.info('User chose to force-transcode')
return True
codec = videoCodec['videocodec']
if codec is None:
# e.g. trailers. Avoids TypeError with "'h265' in codec"
LOG.info('No codec from PMS, not transcoding.')
return False
if ((utils.settings('transcodeHi10P') == 'true' and
videoCodec['bitDepth'] == '10') and
('h264' in codec)):
LOG.info('Option to transcode 10bit h264 video content enabled.')
return True
try:
bitrate = int(videoCodec['bitrate'])
except (TypeError, ValueError):
LOG.info('No video bitrate from PMS, not transcoding.')
return False
if bitrate > _get_max_bitrate():
LOG.info('Video bitrate of %s is higher than the maximal video'
'bitrate of %s that the user chose. Transcoding',
bitrate, _get_max_bitrate())
return True
try:
resolution = int(videoCodec['resolution'])
except (TypeError, ValueError):
if videoCodec['resolution'] == '4k':
resolution = 2160
else:
LOG.info('No video resolution from PMS, not transcoding.')
return False
if 'h265' in codec or 'hevc' in codec:
if resolution >= _getH265():
LOG.info('Option to transcode h265/HEVC enabled. Resolution '
'of the media: %s, transcoding limit resolution: %s',
resolution, _getH265())
return True
return False
def _transcode_quality():
return {
'maxVideoBitrate': get_bitrate(),
'videoResolution': get_resolution(),
'videoQuality': 100,
'mediaBufferSize': int(float(utils.settings('kodi_video_cache')) / 1024.0),
}
def _max_quality():
return {
'maxVideoBitrate': MAX_SIGNED_INT,
'videoResolution': '3840x2160', # 4K
'videoQuality': 100,
'mediaBufferSize': int(float(utils.settings('kodi_video_cache')) / 1024.0),
}
def get_bitrate():
"""
Get the desired transcoding bitrate from the settings
"""
videoQuality = utils.settings('transcoderVideoQualities')
bitrate = {
'0': 320,
'1': 720,
'2': 1500,
'3': 2000,
'4': 3000,
'5': 4000,
'6': 8000,
'7': 10000,
'8': 12000,
'9': 20000,
'10': 40000,
'11': 35000,
'12': 50000
}
# max bit rate supported by server (max signed 32bit integer)
return bitrate.get(videoQuality, MAX_SIGNED_INT)
def get_resolution():
"""
Get the desired transcoding resolutions from the settings
"""
chosen = utils.settings('transcoderVideoQualities')
res = {
'0': '720x480',
'1': '1024x768',
'2': '1280x720',
'3': '1280x720',
'4': '1920x1080',
'5': '1920x1080',
'6': '1920x1080',
'7': '1920x1080',
'8': '1920x1080',
'9': '3840x2160',
'10': '3840x2160'
}
return res[chosen]
def _get_max_bitrate():
max_bitrate = utils.settings('maxVideoQualities')
bitrate = {
'0': 320,
'1': 720,
'2': 1500,
'3': 2000,
'4': 3000,
'5': 4000,
'6': 8000,
'7': 10000,
'8': 12000,
'9': 20000,
'10': 40000,
'11': MAX_SIGNED_INT # deactivated
}
# max bit rate supported by server (max signed 32bit integer)
return bitrate.get(max_bitrate, MAX_SIGNED_INT)
def _getH265():
"""
Returns the user settings for transcoding h265: boundary resolutions
of 480, 720 or 1080 as an int
OR 2147483 (MAX_SIGNED_INT, int) if user chose not to transcode
"""
H265 = {
'0': MAX_SIGNED_INT,
'1': 480,
'2': 720,
'3': 1080,
'4': 2160
}
return H265[utils.settings('transcodeH265')]
def audio_subtitle_prefs(api, item):
"""
Sets the stage for transcoding, letting the user potentially choose both
audio and subtitle streams; subtitle streams to burn-into the video file.
Uses a PUT request to the PMS, simulating e.g. the user using Plex Web,
choosing a different stream in the video's metadata and THEN initiating
playback.
Returns None if user cancelled or we need to abort, True otherwise
"""
# Set media and part where we're at
if api.mediastream is None and api.mediastream_number() is None:
return
if item.playmethod != v.PLAYBACK_METHOD_TRANSCODE:
return True
return setup_transcoding_audio_subtitle_prefs(api.plex_media_streams(),
api.part_id())
def setup_transcoding_audio_subtitle_prefs(mediastreams, part_id):
audio_streams_list = []
audio_streams = []
audio_default = None
subtitle_default = None
subtitle_streams_list = []
# "Don't burn-in any subtitle"
subtitle_streams = ['1 %s' % utils.lang(39706)]
# selectAudioIndex = ""
select_subs_index = ""
audio_numb = 0
# Remember 'no subtitles'
sub_num = 1
for stream in mediastreams:
# Since Plex returns all possible tracks together, have to sort
# them.
index = stream.get('id')
typus = stream.get('streamType')
# Audio
if typus == "2":
codec = stream.get('codec')
channellayout = stream.get('audioChannelLayout', "")
try:
track = "%s %s - %s %s" % (audio_numb + 1,
stream.attrib['language'],
codec,
channellayout)
except KeyError:
track = "%s %s - %s %s" % (audio_numb + 1,
utils.lang(39707), # unknown
codec,
channellayout)
if stream.get('default'):
audio_default = audio_numb
audio_streams_list.append(index)
audio_streams.append(track.encode('utf-8'))
audio_numb += 1
# Subtitles
elif typus == "3":
if stream.get('key'):
# Subtitle can and will be downloaded - don't let user choose
# this subtitle to burn-in
continue
# Subtitle is available within the video file
# Burn in the subtitle, if user chooses to do so
forced = stream.get('forced')
try:
track = '{} {}'.format(sub_num + 1,
stream.attrib['displayTitle'])
except KeyError:
track = '{} {} ({})'.format(sub_num + 1,
utils.lang(39707), # unknown
stream.get('codec'))
if stream.get('default'):
subtitle_default = sub_num
track = "%s - %s" % (track, utils.lang(39708)) # Default
if forced:
track = "%s - %s" % (track, utils.lang(39709)) # Forced
track = "%s (%s)" % (track, utils.lang(39710)) # burn-in
subtitle_streams_list.append(index)
subtitle_streams.append(track.encode('utf-8'))
sub_num += 1
if audio_numb > 1:
# "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
if utils.settings('bestQuality') == 'true' and audio_default is not None:
resp = audio_default
else:
resp = utils.dialog('select', utils.lang(33013), audio_streams)
if resp == -1:
LOG.info('User aborted dialog to select audio stream')
return
args = {
'audioStreamID': audio_streams_list[resp],
'allParts': 1
}
DU().downloadUrl('{server}/library/parts/%s' % part_id,
action_type='PUT',
parameters=args)
# Zero telling the PMS to deactivate subs altogether
select_subs_index = 0
if sub_num == 1:
# Note: we DO need to tell the PMS that we DONT want any sub
# Otherwise, the PMS might pick-up the last one
LOG.info('No subtitles to burn-in')
else:
# "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
if utils.settings('bestQuality') == 'true' and subtitle_default is not None:
resp = subtitle_default
else:
resp = utils.dialog('select', utils.lang(33014), subtitle_streams)
if resp == -1:
LOG.info('User aborted dialog to select subtitle stream')
return
elif resp == 0:
# User did not select a subtitle or backed out of the dialog
LOG.info('User chose to not burn-in any subtitles')
else:
LOG.info('User chose to burn-in subtitle %s: %s',
select_subs_index,
subtitle_streams[resp].decode('utf-8'))
select_subs_index = subtitle_streams_list[resp - 1]
# Now prep the PMS for our choice
PF.change_subtitle(select_subs_index, part_id)
return True

View file

@ -1,73 +1,56 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from threading import Thread
from urlparse import parse_qsl
import playback
from context_entry import ContextMenu
import state
import json_rpc as js
from pickler import pickle_me, Playback_Successful
import kodidb_functions as kodidb
from . import utils, playback, context_entry, transfer, backgroundthread
###############################################################################
LOG = getLogger("PLEX." + __name__)
LOG = getLogger('PLEX.playback_starter')
###############################################################################
class PlaybackStarter(Thread):
class PlaybackTask(backgroundthread.Task):
"""
Processes new plays
"""
@staticmethod
def _triage(item):
def __init__(self, command):
self.command = command
super(PlaybackTask, self).__init__()
def run(self):
LOG.debug('Starting PlaybackTask with %s', self.command)
item = self.command
try:
_, params = item.split('?', 1)
except ValueError:
# E.g. other add-ons scanning for Extras folder
LOG.debug('Detected 3rd party add-on call - ignoring')
pickle_me(Playback_Successful())
transfer.send(True)
# Wait for default.py to have completed xbmcplugin.setResolvedUrl()
transfer.wait_for_transfer(source='default')
return
params = dict(parse_qsl(params))
params = dict(utils.parse_qsl(params))
mode = params.get('mode')
resolve = False if params.get('handle') == '-1' else True
LOG.debug('Received mode: %s, params: %s', mode, params)
if mode == 'play':
if params.get('resume'):
resume = params.get('resume') == '1'
else:
resume = None
playback.playback_triage(plex_id=params.get('plex_id'),
plex_type=params.get('plex_type'),
path=params.get('path'),
resolve=resolve)
resolve=resolve,
resume=resume)
elif mode == 'plex_node':
playback.process_indirect(params['key'],
params['offset'],
resolve=resolve)
elif mode == 'navigation':
# e.g. when plugin://...tvshows is called for entire season
with kodidb.GetKodiDB('video') as kodi_db:
show_id = kodi_db.show_id_from_path(params.get('path'))
if show_id:
js.activate_window('videos',
'videodb://tvshows/titles/%s' % show_id)
else:
LOG.error('Could not find tv show id for %s', item)
if resolve:
pickle_me(Playback_Successful())
elif mode == 'context_menu':
ContextMenu(kodi_id=params.get('kodi_id'),
kodi_type=params.get('kodi_type'))
def run(self):
queue = state.COMMAND_PIPELINE_QUEUE
LOG.info("----===## Starting PlaybackStarter ##===----")
while True:
item = queue.get()
if item is None:
# Need to shutdown - initiated by command_pipeline
break
else:
self._triage(item)
queue.task_done()
LOG.info("----===## PlaybackStarter stopped ##===----")
context_entry.ContextMenu(kodi_id=params.get('kodi_id'),
kodi_type=params.get('kodi_type'))
LOG.debug('Finished PlaybackTask')

View file

@ -1,46 +1,56 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Collection of functions associated with Kodi and Plex playlists and playqueues
"""
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from urllib import quote
from urlparse import parse_qsl, urlsplit
from re import compile as re_compile
import plexdb_functions as plexdb
from downloadutils import DownloadUtils as DU
from utils import try_decode, try_encode
from PlexAPI import API
from PlexFunctions import GetPlexMetadata
from kodidb_functions import kodiid_from_filename
import json_rpc as js
import variables as v
###############################################################################
LOG = getLogger("PLEX." + __name__)
REGEX = re_compile(r'''metadata%2F(\d+)''')
###############################################################################
from .plex_api import API
from .plex_db import PlexDB
from . import plex_functions as PF
from .kodi_db import kodiid_from_filename
from .downloadutils import DownloadUtils as DU
from . import utils
from .utils import cast
from . import json_rpc as js
from . import variables as v
from . import app
from .exceptions import PlaylistError
from .subtitles import accessible_plex_subtitles
class PlaylistError(Exception):
LOG = getLogger('PLEX.playlist_func')
class Playqueue_Object(object):
"""
Exception for our playlist constructs
"""
pass
PKC object to represent PMS playQueues and Kodi playlist for queueing
playlistid = None [int] Kodi playlist id (0, 1, 2)
type = None [str] Kodi type: 'audio', 'video', 'picture'
kodi_pl = None Kodi xbmc.PlayList object
items = [] [list] of Playlist_Items
id = None [str] Plex playQueueID, unique Plex identifier
version = None [int] Plex version of the playQueue
selectedItemID = None
[str] Plex selectedItemID, playing element in queue
selectedItemOffset = None
[str] Offset of the playing element in queue
shuffled = 0 [int] 0: not shuffled, 1: ??? 2: ???
repeat = 0 [int] 0: not repeated, 1: ??? 2: ???
class PlaylistObjectBaseclase(object):
"""
Base class
If Companion playback is initiated by another user:
plex_transient_token = None
"""
kind = 'playQueue'
def __init__(self):
self.playlistid = None
self.id = None
self.type = None
self.playlistid = None
self.kodi_pl = None
self.items = []
self.id = None
self.version = None
self.selectedItemID = None
self.selectedItemOffset = None
@ -54,25 +64,28 @@ class PlaylistObjectBaseclase(object):
self.pkc_edit = False
# Workaround to avoid endless loops of detecting PL clears
self._clear_list = []
# To keep track if Kodi playback was initiated from a Kodi playlist
# There are a couple of pitfalls, unfortunately...
self.kodi_playlist_playback = False
def __repr__(self):
"""
Print the playlist, e.g. to log. Returns utf-8 encoded string
"""
answ = u'{\'%s\': {\'id\': %s, ' % (self.__class__.__name__, self.id)
# For some reason, can't use dir directly
for key in self.__dict__:
if key in ('id', 'items', 'kodi_pl'):
continue
if isinstance(getattr(self, key), str):
answ += '\'%s\': \'%s\', ' % (key,
try_decode(getattr(self, key)))
elif isinstance(getattr(self, key), unicode):
answ += '\'%s\': \'%s\', ' % (key, getattr(self, key))
else:
# e.g. int
answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key)))
return try_encode(answ + '\'items\': %s}}') % self.items
answ = ("{{"
"'playlistid': {self.playlistid}, "
"'id': {self.id}, "
"'version': {self.version}, "
"'type': '{self.type}', "
"'selectedItemID': {self.selectedItemID}, "
"'selectedItemOffset': {self.selectedItemOffset}, "
"'shuffled': {self.shuffled}, "
"'repeat': {self.repeat}, "
"'kodi_playlist_playback': {self.kodi_playlist_playback}, "
"'pkc_edit': {self.pkc_edit}, ".format(self=self))
answ = answ.encode('utf-8')
# Since list.__repr__ will return string, not unicode
return answ + b"'items': {self.items}}}".format(self=self)
def __str__(self):
return self.__repr__()
def is_pkc_clear(self):
"""
@ -106,148 +119,270 @@ class PlaylistObjectBaseclase(object):
self.repeat = 0
self.plex_transient_token = None
self.old_kodi_pl = []
self.kodi_playlist_playback = False
LOG.debug('Playlist cleared: %s', self)
def position_from_plex_id(self, plex_id):
"""
Returns the position [int] for the very first item with plex_id [int]
(Plex seems uncapable of adding the same element multiple times to a
playqueue or playlist)
class Playlist_Object(PlaylistObjectBaseclase):
"""
To be done for synching Plex playlists to Kodi
"""
kind = 'playList'
Raises KeyError if not found
"""
for position, item in enumerate(self.items):
if item.plex_id == plex_id:
break
else:
raise KeyError('Did not find plex_id %s in %s', plex_id, self)
return position
class Playqueue_Object(PlaylistObjectBaseclase):
"""
PKC object to represent PMS playQueues and Kodi playlist for queueing
playlistid = None [int] Kodi playlist id (0, 1, 2)
type = None [str] Kodi type: 'audio', 'video', 'picture'
kodi_pl = None Kodi xbmc.PlayList object
items = [] [list] of Playlist_Items
id = None [str] Plex playQueueID, unique Plex identifier
version = None [int] Plex version of the playQueue
selectedItemID = None
[str] Plex selectedItemID, playing element in queue
selectedItemOffset = None
[str] Offset of the playing element in queue
shuffled = 0 [int] 0: not shuffled, 1: ??? 2: ???
repeat = 0 [int] 0: not repeated, 1: ??? 2: ???
If Companion playback is initiated by another user:
plex_transient_token = None
"""
kind = 'playQueue'
class Playlist_Item(object):
class PlaylistItem(object):
"""
Object to fill our playqueues and playlists with.
id = None [str] Plex playlist/playqueue id, e.g. playQueueItemID
plex_id = None [str] Plex unique item id, "ratingKey"
id = None [int] Plex playlist/playqueue id, e.g. playQueueItemID
plex_id = None [int] Plex unique item id, "ratingKey"
plex_type = None [str] Plex type, e.g. 'movie', 'clip'
plex_uuid = None [str] Plex librarySectionUUID
kodi_id = None Kodi unique kodi id (unique only within type!)
kodi_id = None [int] Kodi unique kodi id (unique only within type!)
kodi_type = None [str] Kodi type: 'movie'
file = None [str] Path to the item's file. STRING!!
uri = None [str] Weird Plex uri path involving plex_uuid. STRING!
uri = None [str] PMS path to item; will be auto-set with plex_id
guid = None [str] Weird Plex guid
xml = None [etree] XML from PMS, 1 lvl below <MediaContainer>
playmethod = None [str] either 'DirectPlay', 'DirectStream', 'Transcode'
api = None [API] API of xml 1 lvl below <MediaContainer>
playmethod = None [str] either 'DirectPath', 'DirectStream', 'Transcode'
playcount = None [int] how many times the item has already been played
offset = None [int] the item's view offset UPON START in Plex time
part = 0 [int] part number if Plex video consists of mult. parts
force_transcode [bool] defaults to False
"""
def __init__(self):
self.id = None
self.plex_id = None
self._plex_id = None
self.plex_type = None
self.plex_uuid = None
self.kodi_id = None
self.kodi_type = None
self.file = None
self.uri = None
self._uri = None
self.guid = None
self.xml = None
self.api = None
self.playmethod = None
self.playcount = None
self.offset = None
# Transcoding quality, if needed
self.quality = None
# If Plex video consists of several parts; part number
self.part = 0
self.force_transcode = False
# Shall we ask user to resume this item?
# None: ask user to resume
# False: do NOT resume, don't ask user
# True: do resume, don't ask user
self.resume = None
# Get the Plex audio and subtitle streams in the same order as Kodi
# uses them (Kodi uses indexes to activate them, not ids like Plex)
self._streams_have_been_processed = False
self._audio_streams = None
self._subtitle_streams = None
# Which Kodi streams are active?
self.current_kodi_audio_stream = None
# False means "deactivated", None means "we do not have a Kodi
# equivalent for this Plex subtitle"
self.current_kodi_sub_stream = None
@property
def plex_id(self):
return self._plex_id
@plex_id.setter
def plex_id(self, value):
self._plex_id = value
self._uri = ('server://%s/com.plexapp.plugins.library/library/metadata/%s' %
(app.CONN.machine_identifier, value))
@property
def uri(self):
return self._uri
@property
def audio_streams(self):
if not self._streams_have_been_processed:
self._process_streams()
return self._audio_streams
@property
def subtitle_streams(self):
if not self._streams_have_been_processed:
self._process_streams()
return self._subtitle_streams
def __unicode__(self):
return ("{{"
"'id': {self.id}, "
"'plex_id': {self.plex_id}, "
"'plex_type': '{self.plex_type}', "
"'kodi_id': {self.kodi_id}, "
"'kodi_type': '{self.kodi_type}', "
"'file': '{self.file}', "
"'guid': '{self.guid}', "
"'playmethod': '{self.playmethod}', "
"'playcount': {self.playcount}, "
"'resume': {self.resume},"
"'offset': {self.offset}, "
"'force_transcode': {self.force_transcode}, "
"'part': {self.part}".format(self=self))
def __repr__(self):
return self.__unicode__().encode('utf-8')
def _process_streams(self):
"""
Print the playlist item, e.g. to log. Returns utf-8 encoded string
Builds audio and subtitle streams and enables matching between Plex
and Kodi using self.audio_streams and self.subtitle_streams
"""
answ = (u'{\'%s\': {\'id\': \'%s\', \'plex_id\': \'%s\', '
% (self.__class__.__name__, self.id, self.plex_id))
for key in self.__dict__:
if key in ('id', 'plex_id', 'xml'):
continue
if isinstance(getattr(self, key), str):
answ += '\'%s\': \'%s\', ' % (key,
try_decode(getattr(self, key)))
elif isinstance(getattr(self, key), unicode):
answ += '\'%s\': \'%s\', ' % (key, getattr(self, key))
else:
# e.g. int
answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key)))
if self.xml is None:
answ += '\'xml\': None}}'
else:
answ += '\'xml\': \'%s\'}}' % self.xml.tag
return try_encode(answ)
# The playqueue response from the PMS does not contain a stream filename
# thanks Plex
self._subtitle_streams = accessible_plex_subtitles(
self.playmethod,
self.file,
self.api.plex_media_streams())
# Audio streams are much easier - they're always available and sorted
# the same in Kodi and Plex
self._audio_streams = [x for x in self.api.plex_media_streams()
if x.get('streamType') == '2']
self._streams_have_been_processed = True
def _get_iterator(self, stream_type):
if stream_type == 'audio':
return self.audio_streams
elif stream_type == 'subtitle':
return self.subtitle_streams
def plex_stream_index(self, kodi_stream_index, stream_type):
"""
Pass in the kodi_stream_index [int] in order to receive the Plex stream
index.
index [int].
stream_type: 'video', 'audio', 'subtitle'
Returns None if unsuccessful
"""
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
count = 0
# Kodi indexes differently than Plex
for stream in self.xml[0][self.part]:
if (stream.attrib['streamType'] == stream_type and
'key' in stream.attrib):
if count == kodi_stream_index:
return stream.attrib['id']
count += 1
for stream in self.xml[0][self.part]:
if (stream.attrib['streamType'] == stream_type and
'key' not in stream.attrib):
if count == kodi_stream_index:
return stream.attrib['id']
count += 1
if stream_type == 'audio':
return int(self.audio_streams[kodi_stream_index].get('id'))
elif stream_type == 'subtitle':
try:
return int(self.subtitle_streams[kodi_stream_index].get('id'))
except (IndexError, TypeError):
pass
def kodi_stream_index(self, plex_stream_index, stream_type):
"""
Pass in the kodi_stream_index [int] in order to receive the Plex stream
index.
Pass in the plex_stream_index [int] in order to receive the Kodi stream
index [int].
stream_type: 'video', 'audio', 'subtitle'
Returns None if unsuccessful
"""
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
count = 0
for stream in self.xml[0][self.part]:
if (stream.attrib['streamType'] == stream_type and
'key' in stream.attrib):
if stream.attrib['id'] == plex_stream_index:
return count
count += 1
for stream in self.xml[0][self.part]:
if (stream.attrib['streamType'] == stream_type and
'key' not in stream.attrib):
if stream.attrib['id'] == plex_stream_index:
return count
count += 1
if plex_stream_index is None:
return
for i, stream in enumerate(self._get_iterator(stream_type)):
if cast(int, stream.get('id')) == plex_stream_index:
return i
def active_plex_stream_index(self, stream_type):
"""
Returns the following tuple for the active stream on the Plex side:
(Plex stream id [int], languageTag [str] or None)
Returns None if no stream has been selected
"""
for i, stream in enumerate(self._get_iterator(stream_type)):
if stream.get('selected') == '1':
return (int(stream.get('id')), stream.get('languageTag'))
def on_kodi_subtitle_stream_change(self, kodi_stream_index, subs_enabled):
"""
Call this method if Kodi changed its subtitle and you want Plex to
know.
"""
if subs_enabled:
try:
plex_stream_index = int(self.subtitle_streams[kodi_stream_index].get('id'))
except (IndexError, TypeError):
LOG.debug('Kodi subtitle change detected to a sub %s that is '
'NOT available on the Plex side', kodi_stream_index)
self.current_kodi_sub_stream = None
return
LOG.debug('Kodi subtitle change detected: telling Plex about '
'switch to index %s, Plex stream id %s',
kodi_stream_index, plex_stream_index)
self.current_kodi_sub_stream = kodi_stream_index
else:
plex_stream_index = 0
LOG.debug('Kodi subtitle has been deactivated, telling Plex')
self.current_kodi_sub_stream = False
PF.change_subtitle(plex_stream_index, self.api.part_id())
def on_kodi_audio_stream_change(self, kodi_stream_index):
"""
Call this method if Kodi changed its audio stream and you want Plex to
know. kodi_stream_index [int]
"""
plex_stream_index = int(self.audio_streams[kodi_stream_index].get('id'))
LOG.debug('Changing Plex audio stream to %s, Kodi index %s',
plex_stream_index, kodi_stream_index)
PF.change_audio_stream(plex_stream_index, self.api.part_id())
self.current_kodi_audio_stream = kodi_stream_index
def switch_to_plex_streams(self):
self.switch_to_plex_stream('audio')
self.switch_to_plex_stream('subtitle')
def switch_to_plex_stream(self, typus):
try:
plex_index, language_tag = self.active_plex_stream_index(typus)
except TypeError:
LOG.debug('Deactivating Kodi subtitles because the PMS '
'told us to not show any subtitles')
app.APP.player.showSubtitles(False)
self.current_kodi_sub_stream = False
return
LOG.debug('The PMS wants to display %s stream with Plex id %s and '
'languageTag %s', typus, plex_index, language_tag)
kodi_index = self.kodi_stream_index(plex_index, typus)
if kodi_index is None:
LOG.debug('Leaving Kodi %s stream settings untouched since we '
'could not parse Plex %s stream with id %s to a Kodi'
' index', typus, typus, plex_index)
else:
LOG.debug('Switching to Kodi %s stream number %s because the '
'PMS told us to show stream with Plex id %s',
typus, kodi_index, plex_index)
# If we're choosing an "illegal" index, this function does
# need seem to fail nor log any errors
if typus == 'audio':
app.APP.player.setAudioStream(kodi_index)
else:
app.APP.player.setSubtitleStream(kodi_index)
app.APP.player.showSubtitles(True)
if typus == 'audio':
self.current_kodi_audio_stream = kodi_index
else:
self.current_kodi_sub_stream = kodi_index
def on_av_change(self, playerid):
kodi_audio_stream = js.get_current_audio_stream_index(playerid)
sub_enabled = js.get_subtitle_enabled(playerid)
kodi_sub_stream = js.get_current_subtitle_stream_index(playerid)
# Audio
if kodi_audio_stream != self.current_kodi_audio_stream:
self.on_kodi_audio_stream_change(kodi_audio_stream)
# Subtitles - CURRENTLY BROKEN ON THE KODI SIDE!
# current_kodi_sub_stream may also be zero
subs_off = (None, False)
if ((sub_enabled and self.current_kodi_sub_stream in subs_off)
or (not sub_enabled and self.current_kodi_sub_stream not in subs_off)
or (kodi_sub_stream is not None
and kodi_sub_stream != self.current_kodi_sub_stream)):
self.on_kodi_subtitle_stream_change(kodi_sub_stream, sub_enabled)
def playlist_item_from_kodi(kodi_item):
@ -257,30 +392,24 @@ def playlist_item_from_kodi(kodi_item):
Supply with data['item'] as returned from Kodi JSON-RPC interface.
kodi_item dict contains keys 'id', 'type', 'file' (if applicable)
"""
item = Playlist_Item()
item = PlaylistItem()
item.kodi_id = kodi_item.get('id')
item.kodi_type = kodi_item.get('type')
if item.kodi_id:
with plexdb.Get_Plex_DB() as plex_db:
plex_dbitem = plex_db.getItem_byKodiId(kodi_item['id'],
kodi_item['type'])
try:
item.plex_id = plex_dbitem[0]
item.plex_type = plex_dbitem[2]
item.plex_uuid = plex_dbitem[0] # we dont need the uuid yet :-)
except TypeError:
pass
with PlexDB(lock=False) as plexdb:
db_item = plexdb.item_by_kodi_id(kodi_item['id'], kodi_item['type'])
if db_item:
item.plex_id = db_item['plex_id']
item.plex_type = db_item['plex_type']
item.file = kodi_item.get('file')
if item.plex_id is None and item.file is not None:
query = dict(parse_qsl(urlsplit(item.file).query))
item.plex_id = query.get('plex_id')
try:
query = item.file.split('?', 1)[1]
except IndexError:
query = ''
query = dict(utils.parse_qsl(query))
item.plex_id = cast(int, query.get('plex_id'))
item.plex_type = query.get('itemType')
if item.plex_id is None and item.file is not None:
item.uri = 'library://whatever/item/%s' % quote(item.file, safe='')
else:
# TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
(item.plex_uuid, item.plex_id))
LOG.debug('Made playlist item from Kodi: %s', item)
return item
@ -294,29 +423,39 @@ def verify_kodi_item(plex_id, kodi_item):
set to None if unsuccessful.
Will raise a PlaylistError if plex_id is None and kodi_item['file'] starts
with either 'plugin' or 'http'
with either 'plugin' or 'http'.
Will raise KeyError if neither plex_id nor kodi_id are found
"""
if plex_id is not None or kodi_item.get('id') is not None:
# Got all the info we need
return kodi_item
# Need more info since we don't have kodi_id nor type. Use file path.
if (kodi_item['file'].startswith('plugin') or
# Special case playlist startup - got type but no id
if (not app.SYNC.direct_paths and app.SYNC.enable_music and
kodi_item.get('type') == v.KODI_TYPE_SONG and
kodi_item['file'].startswith('http')):
kodi_item['id'], _ = kodiid_from_filename(kodi_item['file'],
v.KODI_TYPE_SONG)
LOG.debug('Detected song. Research results: %s', kodi_item)
return kodi_item
# Need more info since we don't have kodi_id nor type. Use file path.
if ((kodi_item['file'].startswith('plugin') and
not kodi_item['file'].startswith('plugin://%s' % v.ADDON_ID)) or
kodi_item['file'].startswith('http')):
LOG.debug('kodi_item cannot be used for Plex playback: %s', kodi_item)
raise PlaylistError('kodi_item cannot be used for Plex playback')
LOG.debug('Starting research for Kodi id since we didnt get one: %s',
kodi_item)
kodi_id = kodiid_from_filename(kodi_item['file'], v.KODI_TYPE_MOVIE)
kodi_item['type'] = v.KODI_TYPE_MOVIE
if kodi_id is None:
kodi_id = kodiid_from_filename(kodi_item['file'],
v.KODI_TYPE_EPISODE)
kodi_item['type'] = v.KODI_TYPE_EPISODE
if kodi_id is None:
kodi_id = kodiid_from_filename(kodi_item['file'],
v.KODI_TYPE_SONG)
kodi_item['type'] = v.KODI_TYPE_SONG
# Try the VIDEO DB first - will find both movies and episodes
kodi_id, kodi_type = kodiid_from_filename(kodi_item['file'],
db_type='video')
if not kodi_id:
# No movie or episode found - try MUSIC DB now for songs
kodi_id, kodi_type = kodiid_from_filename(kodi_item['file'],
db_type='music')
kodi_item['id'] = kodi_id
kodi_item['type'] = None if kodi_id is None else kodi_item['type']
kodi_item['type'] = None if kodi_id is None else kodi_type
if plex_id is None and kodi_id is None:
raise KeyError('Neither Plex nor Kodi id found for %s' % kodi_item)
LOG.debug('Research results for kodi_item: %s', kodi_item)
return kodi_item
@ -327,19 +466,16 @@ def playlist_item_from_plex(plex_id):
Returns a Playlist_Item
"""
item = Playlist_Item()
item = PlaylistItem()
item.plex_id = plex_id
with plexdb.Get_Plex_DB() as plex_db:
plex_dbitem = plex_db.getItem_byId(plex_id)
try:
item.plex_type = plex_dbitem[5]
item.kodi_id = plex_dbitem[0]
item.kodi_type = plex_dbitem[4]
except (TypeError, IndexError):
with PlexDB(lock=False) as plexdb:
db_item = plexdb.item_by_id(plex_id)
if db_item:
item.plex_type = db_item['plex_type']
item.kodi_id = db_item['kodi_id']
item.kodi_type = db_item['kodi_type']
else:
raise KeyError('Could not find plex_id %s in database' % plex_id)
item.plex_uuid = plex_id
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
(item.plex_uuid, plex_id))
LOG.debug('Made playlist item from plex: %s', item)
return item
@ -350,41 +486,42 @@ def playlist_item_from_xml(xml_video_element, kodi_id=None, kodi_type=None):
xml_video_element: etree xml piece 1 level underneath <MediaContainer>
"""
item = Playlist_Item()
item = PlaylistItem()
api = API(xml_video_element)
item.plex_id = api.plex_id()
item.plex_type = api.plex_type()
item.plex_id = api.plex_id
item.plex_type = api.plex_type
# item.id will only be set if you passed in an xml_video_element from e.g.
# a playQueue
item.id = api.item_id()
if kodi_id is not None:
item.kodi_id = kodi_id
item.kodi_type = kodi_type
elif item.plex_id is not None:
with plexdb.Get_Plex_DB() as plex_db:
db_element = plex_db.getItem_byId(item.plex_id)
try:
item.kodi_id, item.kodi_type = db_element[0], db_element[4]
except TypeError:
pass
elif item.plex_type != v.PLEX_TYPE_CLIP:
with PlexDB(lock=False) as plexdb:
db_element = plexdb.item_by_id(item.plex_id,
plex_type=item.plex_type)
if db_element:
item.kodi_id = db_element['kodi_id']
item.kodi_type = db_element['kodi_type']
item.guid = api.guid_html_escaped()
item.playcount = api.viewcount()
item.offset = api.resume_point()
item.xml = xml_video_element
item.api = api
LOG.debug('Created new playlist item from xml: %s', item)
return item
def _get_playListVersion_from_xml(playlist, xml):
def _update_playlist_version(playlist, xml):
"""
Takes a PMS xml as input to overwrite the playlist version (e.g. Plex
Takes a PMS xml (one level above the xml-depth where we're usually applying
API()) as input to overwrite the playlist version (e.g. Plex
playQueueVersion).
Raises PlaylistError if unsuccessful
"""
try:
playlist.version = int(xml.attrib['%sVersion' % playlist.kind])
except (TypeError, AttributeError, KeyError):
playlist.version = int(xml.get('%sVersion' % playlist.kind))
except (AttributeError, TypeError):
raise PlaylistError('Could not get new playlist Version for playlist '
'%s' % playlist)
@ -396,18 +533,16 @@ def get_playlist_details_from_xml(playlist, xml):
Raises PlaylistError if something went wrong.
"""
try:
playlist.id = xml.attrib['%sID' % playlist.kind]
playlist.version = xml.attrib['%sVersion' % playlist.kind]
playlist.shuffled = xml.attrib['%sShuffled' % playlist.kind]
playlist.selectedItemID = xml.attrib.get(
'%sSelectedItemID' % playlist.kind)
playlist.selectedItemOffset = xml.attrib.get(
'%sSelectedItemOffset' % playlist.kind)
LOG.debug('Updated playlist from xml: %s', playlist)
except (TypeError, KeyError, AttributeError) as msg:
raise PlaylistError('Could not get playlist details from xml: %s',
msg)
if xml is None:
raise PlaylistError('No playlist received for playlist %s' % playlist)
playlist.id = cast(int, xml.get('%sID' % playlist.kind))
playlist.version = cast(int, xml.get('%sVersion' % playlist.kind))
playlist.shuffled = cast(int, xml.get('%sShuffled' % playlist.kind))
playlist.selectedItemID = cast(int, xml.get('%sSelectedItemID'
% playlist.kind))
playlist.selectedItemOffset = cast(int, xml.get('%sSelectedItemOffset'
% playlist.kind))
LOG.debug('Updated playlist from xml: %s', playlist)
def update_playlist_from_PMS(playlist, playlist_id=None, xml=None):
@ -416,6 +551,8 @@ def update_playlist_from_PMS(playlist, playlist_id=None, xml=None):
need to fetch a new playqueue
If an xml is passed in, the playlist will be overwritten with its info
Raises PlaylistError if something went wront
"""
if xml is None:
xml = get_PMS_playlist(playlist, playlist_id)
@ -429,16 +566,16 @@ def update_playlist_from_PMS(playlist, playlist_id=None, xml=None):
playlist.items.append(playlist_item)
def init_Plex_playlist(playlist, plex_id=None, kodi_item=None):
def init_plex_playqueue(playlist, plex_id=None, kodi_item=None):
"""
Initializes the Plex side without changing the Kodi playlists
WILL ALSO UPDATE OUR PLAYLISTS.
WILL ALSO UPDATE OUR PLAYLISTS.
Returns the first PKC playlist item or raises PlaylistError
"""
LOG.debug('Initializing the playlist on the Plex side: %s', playlist)
playlist.clear(kodi=False)
LOG.debug('Initializing the playqueue on the Plex side: %s', playlist)
verify_kodi_item(plex_id, kodi_item)
playlist.clear(kodi=False)
try:
if plex_id:
item = playlist_item_from_plex(plex_id)
@ -447,19 +584,23 @@ def init_Plex_playlist(playlist, plex_id=None, kodi_item=None):
params = {
'next': 0,
'type': playlist.type,
'uri': item.uri
'uri': item.uri,
'includeMarkers': 1, # e.g. start + stop of intros
}
xml = DU().downloadUrl(url="{server}/%ss" % playlist.kind,
action_type="POST",
parameters=params)
if xml in (None, 401):
raise PlaylistError('Did not receive a valid xml from the PMS')
get_playlist_details_from_xml(playlist, xml)
# Need to get the details for the playlist item
item = playlist_item_from_xml(xml[0])
except (KeyError, IndexError, TypeError):
raise PlaylistError('Could not init Plex playlist with plex_id %s and '
'kodi_item %s' % (plex_id, kodi_item))
LOG.error('Could not init Plex playlist: plex_id %s, kodi_item %s',
plex_id, kodi_item)
raise PlaylistError
playlist.items.append(item)
LOG.debug('Initialized the playlist on the Plex side: %s', playlist)
LOG.debug('Initialized the playqueue on the Plex side: %s', playlist)
return item
@ -476,9 +617,9 @@ def add_listitem_to_playlist(playlist, pos, listitem, kodi_id=None,
'%s', pos, playlist)
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
if playlist.id is None:
init_Plex_playlist(playlist, plex_id, kodi_item)
init_plex_playqueue(playlist, plex_id, kodi_item)
else:
add_item_to_PMS_playlist(playlist, pos, plex_id, kodi_item)
add_item_to_plex_playqueue(playlist, pos, plex_id, kodi_item)
if kodi_id is None and playlist.items[pos].kodi_id:
kodi_id = playlist.items[pos].kodi_id
kodi_type = playlist.items[pos].kodi_type
@ -505,9 +646,9 @@ def add_item_to_playlist(playlist, pos, kodi_id=None, kodi_type=None,
LOG.debug('add_item_to_playlist. Playlist before adding: %s', playlist)
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
if playlist.id is None:
item = init_Plex_playlist(playlist, plex_id, kodi_item)
item = init_plex_playqueue(playlist, plex_id, kodi_item)
else:
item = add_item_to_PMS_playlist(playlist, pos, plex_id, kodi_item)
item = add_item_to_plex_playqueue(playlist, pos, plex_id, kodi_item)
params = {
'playlistid': playlist.playlistid,
'position': pos
@ -523,7 +664,7 @@ def add_item_to_playlist(playlist, pos, kodi_id=None, kodi_type=None,
return item
def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None):
def add_item_to_plex_playqueue(playlist, pos, plex_id=None, kodi_item=None):
"""
Adds a new item to the playlist at position pos [int] only on the Plex
side of things (e.g. because the user changed the Kodi side)
@ -536,16 +677,22 @@ def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None):
item = playlist_item_from_plex(plex_id)
else:
item = playlist_item_from_kodi(kodi_item)
url = "{server}/%ss/%s?uri=%s" % (playlist.kind, playlist.id, item.uri)
url = "{server}/%ss/%s" % (playlist.kind, playlist.id)
parameters = {
'uri': item.uri,
'includeMarkers': 1, # e.g. start + stop of intros
}
# Will always put the new item at the end of the Plex playlist
xml = DU().downloadUrl(url, action_type="PUT")
xml = DU().downloadUrl(url,
action_type="PUT",
parameters=parameters)
try:
xml[-1].attrib
except (TypeError, AttributeError, KeyError, IndexError):
raise PlaylistError('Could not add item %s to playlist %s'
% (kodi_item, playlist))
api = API(xml[-1])
item.xml = xml[-1]
item.api = api
item.id = api.item_id()
item.guid = api.guid_html_escaped()
item.offset = api.resume_point()
@ -553,7 +700,7 @@ def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None):
playlist.items.append(item)
if pos == len(playlist.items) - 1:
# Item was added at the end
_get_playListVersion_from_xml(playlist, xml)
_update_playlist_version(playlist, xml)
else:
# Move the new item to the correct position
move_playlist_item(playlist,
@ -596,8 +743,8 @@ def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
item = playlist_item_from_kodi(
{'id': kodi_id, 'type': kodi_type, 'file': file})
if item.plex_id is not None:
xml = GetPlexMetadata(item.plex_id)
item.xml = xml[-1]
xml = PF.GetPlexMetadata(item.plex_id)
item.api = API(xml[-1])
playlist.items.insert(pos, item)
return item
@ -621,9 +768,10 @@ def move_playlist_item(playlist, before_pos, after_pos):
playlist.id,
playlist.items[before_pos].id,
playlist.items[after_pos - 1].id)
# We need to increment the playlistVersion
_get_playListVersion_from_xml(
playlist, DU().downloadUrl(url, action_type="PUT"))
# Tell the PMS that we're moving items around
xml = DU().downloadUrl(url, action_type="PUT")
# We need to increment the playlist version for communicating with the PMS
_update_playlist_version(playlist, xml)
# Move our item's position in our internal playlist
playlist.items.insert(after_pos, playlist.items.pop(before_pos))
LOG.debug('Done moving for %s', playlist)
@ -634,16 +782,20 @@ def get_PMS_playlist(playlist, playlist_id=None):
Fetches the PMS playlist/playqueue as an XML. Pass in playlist_id if we
need to fetch a new playlist
Returns None if something went wrong
Raises PlaylistError if something went wrong
"""
playlist_id = playlist_id if playlist_id else playlist.id
xml = DU().downloadUrl(
"{server}/%ss/%s" % (playlist.kind, playlist_id),
headerOptions={'Accept': 'application/xml'})
parameters = {'includeMarkers': 1}
if playlist.kind == 'playList':
xml = DU().downloadUrl("{server}/playlists/%s/items" % playlist_id,
parameters=parameters)
else:
xml = DU().downloadUrl("{server}/playQueues/%s" % playlist_id,
parameters=parameters)
try:
xml.attrib['%sID' % playlist.kind]
except (AttributeError, KeyError):
xml = None
xml.attrib
except AttributeError:
raise PlaylistError('Did not get a valid xml')
return xml
@ -666,8 +818,8 @@ def delete_playlist_item_from_PMS(playlist, pos):
playlist.items[pos].id,
playlist.repeat),
action_type="DELETE")
_get_playListVersion_from_xml(playlist, xml)
del playlist.items[pos]
_update_playlist_version(playlist, xml)
# Functions operating on the Kodi playlist objects ##########
@ -738,9 +890,10 @@ def get_pms_playqueue(playqueue_id):
"""
Returns the Plex playqueue as an etree XML or None if unsuccessful
"""
xml = DU().downloadUrl(
"{server}/playQueues/%s" % playqueue_id,
headerOptions={'Accept': 'application/xml'})
parameters = {'includeMarkers': 1}
xml = DU().downloadUrl("{server}/playQueues/%s" % playqueue_id,
parameters=parameters,
headerOptions={'Accept': 'application/xml'})
try:
xml.attrib
except AttributeError:
@ -757,14 +910,15 @@ def get_plextype_from_xml(xml):
returns None if unsuccessful
"""
try:
plex_id = REGEX.findall(xml.attrib['playQueueSourceURI'])[0]
plex_id = utils.REGEX_PLEX_ID_FROM_URL.findall(
xml.attrib['playQueueSourceURI'])[0]
except IndexError:
LOG.error('Could not get plex_id from xml: %s', xml.attrib)
return
new_xml = GetPlexMetadata(plex_id)
new_xml = PF.GetPlexMetadata(plex_id)
try:
new_xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error('Could not get plex metadata for plex id %s', plex_id)
return
return new_xml[0].attrib.get('type')
return new_xml[0].attrib.get('type').decode('utf-8')

View file

@ -0,0 +1,479 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
:module: plexkodiconnect.playlists
:synopsis: This module syncs Plex playlists to Kodi playlists and vice-versa
:author: Croneter
.. autoclass:: kodi_playlist_monitor
.. autoclass:: full_sync
.. autoclass:: websocket
"""
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from sqlite3 import OperationalError
from .common import Playlist, PlaylistObserver, kodi_playlist_hash
from . import pms, db, kodi_pl, plex_pl
from ..watchdog import events
from ..plex_api import API
from .. import utils, path_ops, variables as v, app
from ..exceptions import PlaylistError
###############################################################################
LOG = getLogger('PLEX.playlists')
# Safety margin for playlist filesystem operations
FILESYSTEM_TIMEOUT = 1
# Which playlist formates are supported by PKC?
SUPPORTED_FILETYPES = (
'm3u',
# 'm3u8'
# 'pls',
# 'cue',
)
###############################################################################
def should_cancel():
return app.APP.stop_pkc or app.SYNC.stop_sync
def kodi_playlist_monitor():
"""
Monitor for the Kodi playlist folder special://profile/playlist
Monitors for all file changes and will thus catch all changes on the Kodi
side of things (as soon as the user saves a new or modified playlist). This
is accomplished by starting a PlaylistObserver with the
PlaylistEventhandler
Returns
-------
PlaylistObserver
Returns an already started PlaylistObserver instance
Notes
-----
Be sure to stop the returned PlaylistObserver with observer.stop()
(and maybe observer.join()) to shut down properly
"""
event_handler = PlaylistEventhandler()
observer = PlaylistObserver(timeout=FILESYSTEM_TIMEOUT)
observer.schedule(event_handler, v.PLAYLIST_PATH, recursive=True)
observer.start()
return observer
def remove_synced_playlists():
"""
Deletes all synched playlists on the Kodi side, not on the Plex side
"""
LOG.info('Removing all playlists that we synced to Kodi')
with app.APP.lock_playlists:
try:
paths = db.get_all_kodi_playlist_paths()
except OperationalError:
LOG.info('Playlists table has not yet been set-up')
return
kodi_pl.delete_kodi_playlists(paths)
db.wipe_table()
LOG.info('Done removing all synced playlists')
def websocket(plex_id, status):
"""
Call this function to process websocket messages from the PMS
Will use the playlist lock to process one single websocket message from
the PMS, and e.g. create or delete the corresponding Kodi playlist (if
applicable settings are set)
Parameters
----------
plex_id : unicode
The unqiue Plex id 'ratingKey' as received from the PMS
status : int
'state' as communicated by the PMS in the websocket message. This
function will then take the correct actions to process the message
* 0: 'created'
* 2: 'matching'
* 3: 'downloading'
* 4: 'loading'
* 5: 'finished'
* 6: 'analyzing'
* 9: 'deleted'
"""
create = False
plex_id = int(plex_id)
with app.APP.lock_playlists:
playlist = db.get_playlist(plex_id=plex_id)
if plex_id in plex_pl.IGNORE_PLEX_PLAYLIST_CHANGE:
LOG.debug('Ignoring detected Plex playlist change for %s',
playlist)
plex_pl.IGNORE_PLEX_PLAYLIST_CHANGE.remove(plex_id)
return
if playlist and status == 9:
# Won't be able to download metadata of the deleted playlist
if sync_plex_playlist(playlist=playlist):
LOG.debug('Plex deletion of playlist detected: %s', playlist)
try:
kodi_pl.delete(playlist)
except PlaylistError:
pass
return
xml = pms.metadata(plex_id)
if xml is None:
LOG.debug('Could not download playlist %s, probably deleted',
plex_id)
return
if not sync_plex_playlist(xml=xml[0]):
return
api = API(xml[0])
try:
if playlist:
if api.updated_at() == playlist.plex_updatedat:
LOG.debug('Playlist with id %s already synced: %s',
plex_id, playlist)
else:
LOG.debug('Change of Plex playlist detected: %s',
playlist)
kodi_pl.delete(playlist)
create = True
elif not playlist and not status == 9:
LOG.debug('Creation of new Plex playlist detected: %s',
plex_id)
create = True
# To the actual work
if create:
kodi_pl.create(plex_id)
except PlaylistError:
pass
def full_sync():
"""
Full sync of playlists between Kodi and Plex
Call to trigger a full sync both ways, e.g. on Kodi start-up. If issues
with a single playlist are encountered on either the Plex or Kodi side,
this particular playlist is omitted. Will use the playlist lock.
Returns
-------
bool
True if successful, False otherwise (actually only if we failed to
fetch the PMS playlists)
"""
LOG.info('Starting playlist full sync')
with app.APP.lock_playlists:
# Need to lock because we're messing with playlists
return _full_sync()
def _full_sync():
# Get all Plex playlists
xml = pms.all_playlists()
if xml is None:
return False
# For each playlist, check Plex database to see whether we already synced
# before. If yes, make sure that hashes are identical. If not, sync it.
old_plex_ids = db.plex_playlist_ids()
for xml_playlist in xml:
if should_cancel():
return False
api = API(xml_playlist)
try:
old_plex_ids.remove(api.plex_id)
except ValueError:
pass
if not sync_plex_playlist(xml=xml_playlist):
continue
playlist = db.get_playlist(plex_id=api.plex_id)
if not playlist:
LOG.debug('New Plex playlist %s discovered: %s',
api.plex_id, api.title())
try:
kodi_pl.create(api.plex_id)
except PlaylistError:
LOG.info('Skipping creation of playlist %s', api.plex_id)
elif playlist.plex_updatedat != api.updated_at():
LOG.debug('Detected changed Plex playlist %s: %s',
api.plex_id, api.title())
# Since we are DELETING a playlist, we need to catch with path!
try:
kodi_pl.delete(playlist)
except PlaylistError:
LOG.info('Skipping recreation of playlist %s', api.plex_id)
else:
try:
kodi_pl.create(api.plex_id)
except PlaylistError:
LOG.info('Could not recreate playlist %s', api.plex_id)
# Get rid of old Plex playlists that were deleted on the Plex side
for plex_id in old_plex_ids:
if should_cancel():
return False
playlist = db.get_playlist(plex_id=plex_id)
LOG.debug('Removing outdated Plex playlist from Kodi: %s', playlist)
if playlist is None:
continue
try:
kodi_pl.delete(playlist)
except PlaylistError:
LOG.debug('Skipping deletion of playlist: %s', playlist)
# Look at all supported Kodi playlists. Check whether they are in the DB.
old_kodi_paths = db.kodi_playlist_paths()
for root, _, files in path_ops.walk(v.PLAYLIST_PATH):
for f in files:
if should_cancel():
return False
path = path_ops.path.join(root, f)
try:
old_kodi_paths.remove(path)
except ValueError:
pass
if not sync_kodi_playlist(path):
continue
kodi_hash = kodi_playlist_hash(path)
playlist = db.get_playlist(path=path)
if playlist and playlist.kodi_hash == kodi_hash:
continue
if not playlist:
LOG.debug('New Kodi playlist detected: %s', path)
playlist = Playlist()
playlist.kodi_path = path
playlist.kodi_hash = kodi_hash
try:
plex_pl.create(playlist)
except PlaylistError:
LOG.info('Skipping Kodi playlist %s', path)
else:
LOG.debug('Changed Kodi playlist detected: %s', path)
plex_pl.delete(playlist)
playlist.kodi_hash = kodi_hash
try:
plex_pl.create(playlist)
except PlaylistError:
LOG.info('Skipping Kodi playlist %s', path)
for kodi_path in old_kodi_paths:
if should_cancel():
return False
playlist = db.get_playlist(path=kodi_path)
if not playlist:
continue
try:
plex_pl.delete(playlist)
except PlaylistError:
LOG.debug('Skipping deletion of Plex playlist: %s', playlist)
LOG.info('Playlist full sync done')
return True
def sync_kodi_playlist(path):
"""
Checks whether we should sync a specific Kodi playlist to Plex
Will check the following conditions for one single Kodi playlist:
* Kodi mixed playlists return False
* Support of the file type of the playlist, e.g. m3u
* Whether filename matches user settings to sync, if enabled
Parameters
----------
path : unicode
Absolute file path to the Kodi playlist in question
Returns
-------
bool
True if we should sync this Kodi playlist to Plex, False otherwise
"""
if path.startswith(v.PLAYLIST_PATH_MIXED):
return False
try:
extension = path.rsplit('.', 1)[1].lower()
except IndexError:
return False
if extension not in SUPPORTED_FILETYPES:
return False
if not app.SYNC.sync_specific_kodi_playlists:
return True
playlist = Playlist()
playlist.kodi_path = path
prefix = utils.settings('syncSpecificKodiPlaylistsPrefix').lower()
if playlist.kodi_filename.lower().startswith(prefix):
return True
LOG.debug('User chose to not sync Kodi playlist %s', path)
return False
def sync_plex_playlist(playlist=None, xml=None, plex_id=None):
"""
Checks whether we should sync a specific Plex playlist to Kodi
Will check the following conditions for one single Plex playlist:
* Plex music playlists return False if PKC audio sync is disabled
* Whether filename matches user settings to sync, if enabled
* False is returned if we could not retrieve more information about the
playlist if only the plex_id was given
Parameters
----------
Pass in either playlist, xml or plex_id (preferably in this order)
plex_id : unicode
Absolute file path to the Kodi playlist in question
xml : etree xml
PMS metadata for the Plex element in question. API(xml) instead of
the usual API(xml[0]) will be used!
playlist: PlayList
A PlayList instance with Playlist.plex_name and PlayList.kodi_type set
Returns
-------
bool
True if we should sync this Plex playlist to Kodi, False otherwise
"""
if playlist:
# Mainly once we DELETED a Plex playlist that we're NOT supposed
# to sync
name = playlist.plex_name
typus = playlist.kodi_type
else:
if xml is None:
xml = pms.metadata(plex_id)
if xml is None:
LOG.info('Could not get Plex metadata for playlist %s',
plex_id)
return False
api = API(xml[0])
else:
api = API(xml)
if api.playlist_type() == v.PLEX_TYPE_PHOTO_PLAYLIST:
# Not supported by Kodi
return False
elif api.playlist_type() is None:
# Encountered in logs, seems to be a malformed answer
LOG.error('Playlist type is missing: %s', api.xml.attrib)
return False
name = api.title()
typus = v.KODI_PLAYLIST_TYPE_FROM_PLEX[api.playlist_type()]
if (not app.SYNC.enable_music and typus == v.PLEX_PLAYLIST_TYPE_AUDIO):
LOG.debug('Not synching Plex audio playlist')
return False
if not app.SYNC.sync_specific_plex_playlists:
return True
prefix = utils.settings('syncSpecificPlexPlaylistsPrefix').lower()
if name and name.lower().startswith(prefix):
return True
LOG.debug('User chose to not sync Plex playlist %s', name)
return False
class PlaylistEventhandler(events.FileSystemEventHandler):
"""
PKC eventhandler to monitor Kodi playlists safed to disk
"""
def dispatch(self, event):
"""
Dispatches events to the appropriate methods.
Parameters
----------
:type event:
:class:`FileSystemEvent`
The event object representing the file system event.
"""
path = event.dest_path if event.event_type == events.EVENT_TYPE_MOVED \
else event.src_path
with app.APP.lock_playlists:
if not sync_kodi_playlist(path):
return
if path in kodi_pl.IGNORE_KODI_PLAYLIST_CHANGE:
LOG.debug('Ignoring event %s', event)
kodi_pl.IGNORE_KODI_PLAYLIST_CHANGE.remove(path)
return
_method_map = {
events.EVENT_TYPE_MODIFIED: self.on_modified,
events.EVENT_TYPE_MOVED: self.on_moved,
events.EVENT_TYPE_CREATED: self.on_created,
events.EVENT_TYPE_DELETED: self.on_deleted,
}
_method_map[event.event_type](event)
def on_created(self, event):
LOG.debug('on_created: %s', event.src_path)
old_playlist = db.get_playlist(path=event.src_path)
kodi_hash = kodi_playlist_hash(event.src_path)
if old_playlist and old_playlist.kodi_hash == kodi_hash:
LOG.debug('Playlist already in DB - skipping')
return
elif old_playlist:
LOG.debug('Playlist already in DB but it has been changed')
self.on_modified(event)
return
playlist = Playlist()
playlist.kodi_path = event.src_path
playlist.kodi_hash = kodi_hash
try:
plex_pl.create(playlist)
except PlaylistError:
pass
def on_modified(self, event):
LOG.debug('on_modified: %s', event.src_path)
old_playlist = db.get_playlist(path=event.src_path)
kodi_hash = kodi_playlist_hash(event.src_path)
if old_playlist and old_playlist.kodi_hash == kodi_hash:
LOG.debug('Nothing modified, playlist already in DB - skipping')
return
new_playlist = Playlist()
if old_playlist:
# Retain the name! Might've come from Plex
# (rename should fire on_moved)
new_playlist.plex_name = old_playlist.plex_name
plex_pl.delete(old_playlist)
new_playlist.kodi_path = event.src_path
new_playlist.kodi_hash = kodi_hash
try:
plex_pl.create(new_playlist)
except PlaylistError:
pass
def on_moved(self, event):
LOG.debug('on_moved: %s to %s', event.src_path, event.dest_path)
kodi_hash = kodi_playlist_hash(event.dest_path)
# First check whether we don't already have destination playlist in
# our DB. Just in case....
old_playlist = db.get_playlist(path=event.dest_path)
if old_playlist:
LOG.warning('Found target playlist already in our DB!')
new_event = events.FileModifiedEvent(event.dest_path)
self.on_modified(new_event)
return
# All good
old_playlist = db.get_playlist(path=event.src_path)
if not old_playlist:
LOG.debug('Did not have source path in the DB %s', event.src_path)
else:
plex_pl.delete(old_playlist)
new_playlist = Playlist()
new_playlist.kodi_path = event.dest_path
new_playlist.kodi_hash = kodi_hash
try:
plex_pl.create(new_playlist)
except PlaylistError:
pass
def on_deleted(self, event):
LOG.debug('on_deleted: %s', event.src_path)
playlist = db.get_playlist(path=event.src_path)
if not playlist:
LOG.debug('Playlist not found in DB for path %s', event.src_path)
else:
plex_pl.delete(playlist)

View file

@ -0,0 +1,207 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import Queue
import time
import os
import hashlib
from ..watchdog import events
from ..watchdog.observers import Observer
from ..watchdog.utils.bricks import OrderedSetQueue
from .. import path_ops, variables as v, app
from ..exceptions import PlaylistError
###############################################################################
LOG = getLogger('PLEX.playlists.common')
# These filesystem events are considered similar
SIMILAR_EVENTS = (events.EVENT_TYPE_CREATED, events.EVENT_TYPE_MODIFIED)
###############################################################################
class Playlist(object):
"""
Class representing a synced Playlist with info for both Kodi and Plex.
Attributes:
Plex:
plex_id: unicode
plex_name: unicode
plex_updatedat: unicode
Kodi:
kodi_path: unicode
kodi_filename: unicode
kodi_extension: unicode
kodi_type: unicode
kodi_hash: unicode
Testing for a Playlist() returns ONLY True if all the following attributes
are set; 2 playlists are only equal if all attributes are equal:
plex_id
plex_name
plex_updatedat
kodi_path
kodi_filename
kodi_type
kodi_hash
"""
def __init__(self):
# Plex
self.plex_id = None
self.plex_name = None
self.plex_updatedat = None
# Kodi
self._kodi_path = None
self.kodi_filename = None
self.kodi_extension = None
self.kodi_type = None
self.kodi_hash = None
def __unicode__(self):
return ("{{"
"'plex_id': {self.plex_id}, "
"'plex_name': '{self.plex_name}', "
"'kodi_type': '{self.kodi_type}', "
"'kodi_filename': '{self.kodi_filename}', "
"'kodi_path': '{self._kodi_path}', "
"'plex_updatedat': {self.plex_updatedat}, "
"'kodi_hash': '{self.kodi_hash}'"
"}}").format(self=self)
def __repr__(self):
return self.__unicode__().encode('utf-8')
def __str__(self):
return self.__repr__()
def __bool__(self):
return (self.plex_id and self.plex_updatedat and self.plex_name and
self._kodi_path and self.kodi_filename and self.kodi_type and
self.kodi_hash)
# Used for comparison of playlists
@property
def key(self):
return (self.plex_id, self.plex_updatedat, self.plex_name,
self._kodi_path, self.kodi_filename, self.kodi_type,
self.kodi_hash)
def __eq__(self, playlist):
return self.key == playlist.key
def __ne__(self, playlist):
return self.key != playlist.key
@property
def kodi_path(self):
return self._kodi_path
@kodi_path.setter
def kodi_path(self, path):
f = path_ops.path.basename(path)
try:
self.kodi_filename, self.kodi_extension = f.rsplit('.', 1)
except ValueError:
LOG.error('Trying to set invalid path: %s', path)
raise PlaylistError('Invalid path: %s' % path)
if path.startswith(v.PLAYLIST_PATH_VIDEO):
self.kodi_type = v.KODI_TYPE_VIDEO_PLAYLIST
elif path.startswith(v.PLAYLIST_PATH_MUSIC):
self.kodi_type = v.KODI_TYPE_AUDIO_PLAYLIST
else:
LOG.error('Playlist type not supported for %s', path)
raise PlaylistError('Playlist type not supported: %s' % path)
if not self.plex_name:
self.plex_name = self.kodi_filename
self._kodi_path = path
def kodi_playlist_hash(path):
"""
Returns a md5 hash [unicode] using os.stat() st_size and st_mtime for the
playlist located at path [unicode]
(size of file in bytes and time of most recent content modification)
There are probably way more efficient ways out there to do this
"""
stat = os.stat(path_ops.encode_path(path))
# stat.st_size is of type long; stat.st_mtime is of type float - hash both
m = hashlib.md5()
m.update(repr(stat.st_size))
m.update(repr(stat.st_mtime))
return m.hexdigest().decode('utf-8')
class PlaylistQueue(OrderedSetQueue):
"""
OrderedSetQueue that drops all directory events immediately
"""
def _put(self, item):
if item[0].is_directory:
self.unfinished_tasks -= 1
else:
# Can't use super as OrderedSetQueue is old style class
OrderedSetQueue._put(self, item)
class PlaylistObserver(Observer):
"""
PKC implementation, overriding the dispatcher. PKC will wait for the
duration timeout (in seconds) AFTER receiving a filesystem event. A new
("non-similar") event will reset the timer.
Creating and modifying will be regarded as equal.
"""
def __init__(self, *args, **kwargs):
super(PlaylistObserver, self).__init__(*args, **kwargs)
# Drop the same events that get into the queue even if there are other
# events in between these similar events. Ignore directory events
# completely
self._event_queue = PlaylistQueue()
@staticmethod
def _pkc_similar_events(event1, event2):
if event1 == event2:
return True
elif (event1.src_path == event2.src_path and
event1.event_type in SIMILAR_EVENTS and
event2.event_type in SIMILAR_EVENTS):
# Set created and modified events to equal
return True
return False
def _dispatch_iterator(self, event_queue, timeout):
"""
This iterator will block for timeout (seconds) until an event is
received or raise Queue.Empty.
"""
event, watch = event_queue.get(block=True, timeout=timeout)
event_queue.task_done()
start = time.time()
while time.time() - start < timeout:
try:
new_event, new_watch = event_queue.get(block=False)
except Queue.Empty:
app.APP.monitor.waitForAbort(0.2)
else:
event_queue.task_done()
start = time.time()
if self._pkc_similar_events(new_event, event):
continue
else:
yield event, watch
event, watch = new_event, new_watch
yield event, watch
def dispatch_events(self, event_queue, timeout):
for event, watch in self._dispatch_iterator(event_queue, timeout):
# This is copy-paste of original code
with self._lock:
# To allow unschedule/stop and safe removal of event handlers
# within event handlers itself, check if the handler is still
# registered after every dispatch.
for handler in list(self._handlers.get(watch, [])):
if handler in self._handlers.get(watch, []):
handler.dispatch(event)

View file

@ -0,0 +1,134 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Synced playlists are stored in our plex.db. Interact with it through this
module
"""
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .common import Playlist
from ..plex_db import PlexDB
from ..kodi_db import kodiid_from_filename
from .. import path_ops, utils, variables as v
from ..exceptions import PlaylistError
###############################################################################
LOG = getLogger('PLEX.playlists.db')
###############################################################################
def plex_playlist_ids():
"""
Returns a list of all Plex ids of the playlists already in our DB
"""
with PlexDB() as plexdb:
return list(plexdb.playlist_ids())
def kodi_playlist_paths():
"""
Returns a list of all Kodi playlist paths of the playlists already synced
"""
with PlexDB() as plexdb:
return list(plexdb.kodi_playlist_paths())
def update_playlist(playlist, delete=False):
"""
Assumes that all sync operations are over. Takes playlist [Playlist]
and creates/updates the corresponding Plex playlists table entry
Pass delete=True to delete the playlist entry
"""
with PlexDB() as plexdb:
if delete:
plexdb.delete_playlist(playlist)
else:
plexdb.add_playlist(playlist)
def get_playlist(path=None, plex_id=None):
"""
Returns the playlist as a Playlist for either the plex_id or path
"""
playlist = Playlist()
with PlexDB() as plexdb:
playlist = plexdb.playlist(playlist, plex_id, path)
return playlist
def get_all_kodi_playlist_paths():
"""
Returns a list with all paths for the playlists on the Kodi side
"""
with PlexDB() as plexdb:
paths = list(plexdb.all_kodi_paths())
return paths
def wipe_table():
"""
Deletes all playlists entries in the Plex DB
"""
with PlexDB() as plexdb:
plexdb.wipe_playlists()
def _m3u_iterator(text):
"""
Yields e.g. plugin://plugin.video.plexkodiconnect.movies/?plex_id=xxx
"""
lines = iter(text.split('\n'))
for line in lines:
if line.startswith('#EXTINF:'):
next_line = next(lines).strip()
if next_line.startswith('#EXT-KX-OFFSET:'):
yield next(lines).strip()
else:
yield next_line
def m3u_to_plex_ids(playlist):
"""
Adapter to process *.m3u playlist files. Encoding is not uniform!
"""
plex_ids = list()
with open(path_ops.encode_path(playlist.kodi_path), 'rb') as f:
text = f.read()
try:
text = text.decode(v.M3U_ENCODING)
except UnicodeDecodeError:
LOG.warning('Fallback to ISO-8859-1 decoding for %s', playlist)
text = text.decode('ISO-8859-1')
for entry in _m3u_iterator(text):
plex_id = utils.REGEX_PLEX_ID.search(entry)
if plex_id:
plex_id = plex_id.group(1)
plex_ids.append(plex_id)
else:
# Add-on paths not working, try direct
kodi_id, kodi_type = kodiid_from_filename(entry,
db_type=playlist.kodi_type)
if not kodi_id:
continue
with PlexDB() as plexdb:
item = plexdb.item_by_kodi_id(kodi_id, kodi_type)
if item:
plex_ids.append(item['plex_id'])
return plex_ids
def playlist_file_to_plex_ids(playlist):
"""
Takes the playlist file located at path [unicode] and parses it.
Returns a list of plex_ids (str) or raises PlaylistError if a single
item cannot be parsed from Kodi to Plex.
"""
if playlist.kodi_extension == 'm3u':
plex_ids = m3u_to_plex_ids(playlist)
else:
LOG.error('Unsupported playlist extension: %s',
playlist.kodi_extension)
raise PlaylistError
return plex_ids

View file

@ -0,0 +1,175 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Create and delete playlists on the Kodi side of things
"""
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import re
from .common import Playlist, kodi_playlist_hash
from . import db, pms
from ..plex_api import API
from .. import utils, path_ops, variables as v
from ..exceptions import PlaylistError
###############################################################################
LOG = getLogger('PLEX.playlists.kodi_pl')
REGEX_FILE_NUMBERING = re.compile(r'''_(\d\d)\.\w+$''')
# Avoid endless loops. Store the Kodi paths
IGNORE_KODI_PLAYLIST_CHANGE = list()
###############################################################################
def create(plex_id):
"""
Creates a new Kodi playlist file. Will also add (or modify an existing)
Plex playlist table entry.
Assumes that the Plex playlist is indeed new. A NEW Kodi playlist will be
created in any case (not replaced). Thus make sure that the "same" playlist
is deleted from both disk and the Plex database.
Returns the playlist or raises PlaylistError
"""
xml_metadata = pms.metadata(plex_id)
if xml_metadata is None:
LOG.error('Could not get Plex playlist metadata %s', plex_id)
raise PlaylistError('Could not get Plex playlist %s' % plex_id)
api = API(xml_metadata[0])
playlist = Playlist()
playlist.plex_id = api.plex_id
playlist.kodi_type = v.KODI_PLAYLIST_TYPE_FROM_PLEX[api.playlist_type()]
playlist.plex_name = api.title()
playlist.plex_updatedat = api.updated_at()
LOG.debug('Creating new Kodi playlist from Plex playlist: %s', playlist)
# Derive filename close to Plex playlist name
name = utils.valid_filename(playlist.plex_name)
path = path_ops.path.join(v.PLAYLIST_PATH, playlist.kodi_type,
'%s.m3u' % name)
while path_ops.exists(path) or db.get_playlist(path=path):
# In case the Plex playlist names are not unique
occurance = REGEX_FILE_NUMBERING.search(path)
if not occurance:
path = path_ops.path.join(v.PLAYLIST_PATH,
playlist.kodi_type,
'%s_01.m3u' % name[:min(len(name), 248)])
else:
number = int(occurance.group(1)) + 1
if number > 3:
LOG.warn('Detected spanning tree issue, abort sync for %s',
playlist)
raise PlaylistError('Spanning tree warning')
basename = re.sub(REGEX_FILE_NUMBERING, '', path)
path = '%s_%02d.m3u' % (basename, number)
LOG.debug('Kodi playlist path: %s', path)
playlist.kodi_path = path
xml_playlist = pms.get_playlist(plex_id)
if xml_playlist is None:
LOG.error('Could not get Plex playlist %s', plex_id)
raise PlaylistError('Could not get Plex playlist %s' % plex_id)
IGNORE_KODI_PLAYLIST_CHANGE.append(playlist.kodi_path)
try:
_write_playlist_to_file(playlist, xml_playlist)
except Exception:
IGNORE_KODI_PLAYLIST_CHANGE.remove(playlist.kodi_path)
raise
playlist.kodi_hash = kodi_playlist_hash(path)
db.update_playlist(playlist)
LOG.debug('Created Kodi playlist based on Plex playlist: %s', playlist)
def delete(playlist):
"""
Removes the corresponding Kodi file for playlist Playlist from
disk. Be sure that playlist.kodi_path is set. Will also delete the entry in
the Plex playlist table.
Returns None or raises PlaylistError
"""
if path_ops.exists(playlist.kodi_path):
IGNORE_KODI_PLAYLIST_CHANGE.append(playlist.kodi_path)
try:
path_ops.remove(playlist.kodi_path)
LOG.debug('Deleted Kodi playlist: %s', playlist)
except (OSError, IOError) as err:
LOG.error('Could not delete Kodi playlist file %s. Error:\n%s: %s',
playlist, err.errno, err.strerror)
IGNORE_KODI_PLAYLIST_CHANGE.remove(playlist.kodi_path)
raise PlaylistError('Could not delete %s' % playlist.kodi_path)
db.update_playlist(playlist, delete=True)
def delete_kodi_playlists(playlist_paths):
"""
Deletes all the the playlist files passed in; WILL IGNORE THIS CHANGE ON
THE PLEX SIDE!
"""
for path in playlist_paths:
try:
path_ops.remove(path)
# Ensure we're not deleting the playlists on the Plex side later
IGNORE_KODI_PLAYLIST_CHANGE.append(path)
LOG.info('Removed playlist %s', path)
except (OSError, IOError):
LOG.warn('Could not remove playlist %s', path)
def _write_playlist_to_file(playlist, xml):
"""
Feed with playlist Playlist. Will write the playlist to a m3u file
Returns None or raises PlaylistError
"""
text = '#EXTCPlayListM3U::M3U\n'
for xml_element in xml:
text += _m3u_element(xml_element)
text += '\n'
text = text.encode(v.M3U_ENCODING, 'ignore')
try:
with open(path_ops.encode_path(playlist.kodi_path), 'wb') as f:
f.write(text)
except EnvironmentError as err:
LOG.error('Could not write Kodi playlist file: %s', playlist)
LOG.error('Error message %s: %s', err.errno, err.strerror)
raise PlaylistError('Cannot write Kodi playlist to path for %s'
% playlist)
def _m3u_element(xml_element):
api = API(xml_element)
if api.plex_type == v.PLEX_TYPE_EPISODE:
if api.season_number() is not None and api.index() is not None:
return '#EXTINF:{},{} S{:2d}E{:2d} - {}\n{}\n'.format(
api.runtime(),
api.show_title(),
api.season_number(),
api.index(),
api.title(),
api.fullpath(force_addon=True)[0])
else:
# Only append the TV show name
return '#EXTINF:{},{} - {}\n{}\n'.format(
api.runtime(),
api.show_title(),
api.title(),
api.fullpath(force_addon=True)[0])
elif api.plex_type == v.PLEX_TYPE_SONG:
if api.index() is not None:
return '#EXTINF:{},{:02d}. {} - {}\n{}\n'.format(
api.runtime(),
api.index(),
api.grandparent_title(),
api.title(),
api.fullpath(force_first_media=True,
omit_check=True)[0])
else:
return '#EXTINF:{},{} - {}\n{}\n'.format(
api.runtime(),
api.grandparent_title(),
api.title(),
api.fullpath(force_first_media=True,
omit_check=True)[0])
else:
return '#EXTINF:{},{}\n{}\n'.format(
api.runtime(),
api.title(),
api.fullpath(force_first_media=True,
omit_check=True)[0])

View file

@ -0,0 +1,49 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Create and delete playlists on the Plex side of things
"""
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from . import pms, db
from ..exceptions import PlaylistError
###############################################################################
LOG = getLogger('PLEX.playlists.plex_pl')
# Used for updating Plex playlists due to Kodi changes - Plex playlist
# will have to be deleted first. Add Plex ids!
IGNORE_PLEX_PLAYLIST_CHANGE = list()
###############################################################################
def create(playlist):
"""
Adds the playlist Playlist to the PMS. If playlist.id is
not None the existing Plex playlist will be overwritten; otherwise a new
playlist will be generated and stored accordingly in the playlist object.
Will also add (or modify an existing) Plex playlist table entry.
Make sure that playlist.kodi_hash is set!
Returns None or raises PlaylistError
"""
LOG.debug('Creating Plex playlist from Kodi file: %s', playlist)
plex_ids = db.playlist_file_to_plex_ids(playlist)
if not plex_ids:
LOG.warning('No Plex ids found for playlist %s', playlist)
raise PlaylistError
pms.add_items(playlist, plex_ids)
IGNORE_PLEX_PLAYLIST_CHANGE.append(playlist.plex_id)
db.update_playlist(playlist)
LOG.debug('Done creating Plex playlist %s', playlist)
def delete(playlist):
"""
Removes the playlist Playlist from the PMS. Will also delete the
entry in the Plex playlist table.
Returns None or raises PlaylistError
"""
LOG.debug('Deleting playlist from PMS: %s', playlist)
IGNORE_PLEX_PLAYLIST_CHANGE.append(playlist.plex_id)
pms.delete(playlist)
db.update_playlist(playlist, delete=True)

Some files were not shown because too many files have changed in this diff Show more