Compare commits

...

403 commits

Author SHA1 Message Date
croneter
10ff206c58
Merge pull request #1686 from croneter/python3-beta
Bump Python 3 master
2021-10-30 18:45:29 +02:00
croneter
ce37820649
Merge pull request #1685 from croneter/py3-version-bump
Stable and beta version bump 3.5.8
2021-10-30 18:44:33 +02:00
croneter
902c56b8f7
Merge pull request #1683 from croneter/py3-fix-unbound
Fix UnboundLocalError: local variable 'identifier' referenced before assignment
2021-10-30 18:44:20 +02:00
croneter
59a3dd0010 Stable and beta version bump 3.5.8 2021-10-30 18:43:38 +02:00
croneter
ab52521f73 Fix UnboundLocalError: local variable 'identifier' referenced before assignment 2021-10-30 18:34:35 +02:00
croneter
9f18288e80
Merge pull request #1679 from croneter/py3-version-bump
Beta version bump 3.5.7
2021-10-22 21:23:19 +02:00
croneter
cf149558be
Merge pull request #1678 from croneter/py3-fix-playback-startup
Fix Kodi JSON racing condition on playback startup and KeyError
2021-10-22 21:22:38 +02:00
croneter
d605cfd685 Beta version bump 3.5.7 2021-10-22 21:22:32 +02:00
croneter
20f5d9d561 Fix Kodi JSON racing condition on playback startup and KeyError 2021-10-22 21:20:08 +02:00
croneter
2850889c98
Merge pull request #1675 from croneter/py3-version-bump
Beta version bump 3.5.6
2021-10-20 14:56:10 +02:00
croneter
937265dfc9
Merge pull request #1674 from croneter/py3-fix-socketserver
Fix Plex Companion not working by fixing some issues with PKC's http.server's BaseHTTPRequestHandler
2021-10-20 14:55:58 +02:00
croneter
49fce5a2cd Track video streams as well; refactor reporting of playback progress; only switch audio stream if different 2021-10-20 14:55:39 +02:00
croneter
0385c25e2f Beta version bump 3.5.6 2021-10-20 14:53:12 +02:00
croneter
4f7e54591c Fix TypeError for Plex Companion switching audio or subtitle stream 2021-10-19 08:36:05 +02:00
croneter
0d41321d7f Fix PKC not being able to register as a PMS client 2021-10-18 16:51:09 +02:00
croneter
aa14e8259f Fix Plex Web's Plex Companion connection terminating suddenly 2021-10-18 16:51:09 +02:00
croneter
f3754fa2e3 Use ThreadingHTTPServer instead of own threaded HTTPServer 2021-10-18 16:39:12 +02:00
croneter
8c64e2a17c Fix http server headers Connection:keep-alive and Content-Type for XMLs 2021-10-18 16:39:11 +02:00
croneter
e6171127dc Don't simply swallow all http server exceptions 2021-10-18 16:39:11 +02:00
croneter
d37fbb6c1a Remove obsolete BaseHTTPRequestHandler method 2021-10-18 16:39:11 +02:00
croneter
c107eb2ed8 Do not send a Connection: close header 2021-10-18 16:39:11 +02:00
croneter
492e235a53 If receiving a Companion request, reply with a code 200 xml 2021-10-18 16:39:11 +02:00
croneter
4d3e36fbdb Add missing HTTP headers that Plex for Window's Plex Companion uses 2021-10-18 16:39:11 +02:00
croneter
d8db463423 Improve logging for http client 2021-10-18 16:39:11 +02:00
croneter
f2e03e878e Do not close http output stream when responding 2021-10-18 09:53:26 +02:00
croneter
56a3cdbbd8 Use super() for subclassing BaseHTTPRequestHandler 2021-10-18 08:58:09 +02:00
croneter
741ae76cf9
Merge pull request #1666 from croneter/python3-beta
Bump Python 3 master
2021-10-17 11:39:15 +02:00
croneter
e0451e4a15
Merge pull request #1665 from croneter/py3-version-bump
Stable and beta version bump 3.5.5
2021-10-17 11:38:36 +02:00
croneter
58e800b9d9
Merge pull request #1664 from croneter/py3-drop-multiprocessing
Lost patience with Kodi 19: drop use of Python multiprocessing entirely
2021-10-17 11:38:28 +02:00
croneter
7d37da6177 Stable and beta version bump 3.5.5 2021-10-17 11:37:43 +02:00
croneter
ff5df50bb7 Lost patience with Kodi 19: drop use of Python multiprocessing entirely 2021-10-17 11:34:08 +02:00
croneter
048f11a4ce
Merge pull request #1663 from croneter/python3-beta
Bump Python 3 Master
2021-10-16 11:47:09 +02:00
croneter
aa917a233f
Merge pull request #1662 from croneter/py3-version-bump
Stable and beta version bump 3.5.4
2021-10-16 11:46:17 +02:00
croneter
e0e899c6d0
Merge pull request #1661 from croneter/py3-fix-tmdb
Fix Receiving init() missing 1 required positional argument: ‘certification_country’
2021-10-16 11:46:02 +02:00
croneter
8bf9ca5a15
Merge pull request #1655 from croneter/py3-update-translations
Update translations from Transifex
2021-10-16 11:45:50 +02:00
croneter
87eda6a207 Stable and beta version bump 3.5.4 2021-10-16 11:45:32 +02:00
croneter
56755cf506 Fix Receiving init() missing 1 required positional argument: ‘certification_country’ 2021-10-16 11:40:43 +02:00
croneter
b2634d6f96 Update translations from Transifex 2021-10-09 18:09:12 +02:00
croneter
389be72ee2
Merge pull request #1654 from croneter/python3-beta
Bump Python 3 master
2021-10-09 17:27:30 +02:00
croneter
3a095e6ef6
Merge pull request #1653 from croneter/py3-version-bump
Stable and beta version bump 3.5.3
2021-10-09 17:26:48 +02:00
croneter
d867ac537d
Merge pull request #1652 from croneter/py3-stream-option
Add playback settings to let the user choose whether Plex or Kodi provides the default audio or subtitle stream on playback start
2021-10-09 17:26:39 +02:00
croneter
3b7c6c535b Stable and beta version bump 3.5.3 2021-10-09 17:26:02 +02:00
croneter
266e2975a9 Add playback settings to let the user choose whether Plex or Kodi provides the default audio and subtitle streams on playback start 2021-10-09 17:23:47 +02:00
croneter
2d636d8e08
Merge pull request #1648 from croneter/python3-beta
Bump Python 3 master
2021-10-04 15:43:17 +02:00
croneter
c737e1f662
Merge pull request #1647 from croneter/py3-version-bump
Stable and beta version bump 3.5.2
2021-10-04 15:42:10 +02:00
croneter
a7e4d49db1 Stable and beta version bump 3.5.2 2021-10-04 15:40:25 +02:00
croneter
11572f8284
Merge pull request #1645 from croneter/py3-version-bump
Beta version bump 3.5.1
2021-09-30 14:57:58 +02:00
croneter
d4f9dd427f
Merge pull request #1644 from croneter/py3-fix-multiprocessing
Android: Fix broken Python multiprocessing module (a Kodi 19.2 bug)
2021-09-30 14:57:33 +02:00
croneter
7e3a1c9ddc
Merge pull request #1643 from croneter/py3-fix-subs
Refactor stream code and fix Kodi not activating subtitle when it should
2021-09-30 14:57:21 +02:00
croneter
5f0a256a16
Merge pull request #1642 from croneter/py3-fix-typeerror
Direct Paths: Fix TypeError: "element indices must be integers" on playback startup
2021-09-30 14:57:02 +02:00
croneter
678e4b5ab1
Merge pull request #1633 from croneter/py3-fix-logging
Fix logging if fanart.tv lookup fails: be less verbose
2021-09-30 14:56:37 +02:00
croneter
6e0c3c6567 Beta version bump 3.5.1 2021-09-30 14:55:42 +02:00
croneter
3d535fe2bf Android: Fix broken Python multiprocessing module (a Kodi 19.2 bug) 2021-09-30 14:33:43 +02:00
croneter
79e912e2bb Direct Paths: Fix TypeError: element indices must be integers for subtitles 2021-09-30 14:17:52 +02:00
croneter
fa28ebfac1 Fix whitespace 2021-09-30 13:31:23 +02:00
croneter
fee6e23a23 Refactor and fix Kodi not activating subtitle when it should 2021-09-30 13:12:37 +02:00
croneter
594ca9b667 Fix logging if fanart.tv lookup fails 2021-09-24 16:24:57 +02:00
croneter
33ad095080 Fix download not always returning entire requests.response object 2021-09-24 16:02:32 +02:00
croneter
6c44b5e392
Merge pull request #1632 from croneter/python3-beta
Bump python3 master
2021-09-24 14:26:15 +02:00
croneter
352b36d32b
Merge pull request #1631 from croneter/py3-version-bump
Stable and beta version bump 3.5.0
2021-09-24 14:25:20 +02:00
croneter
b5e6ecf9d5 Stable and beta version bump 3.5.0 2021-09-24 14:24:41 +02:00
croneter
71777d80ef
Merge pull request #1625 from croneter/py3-version-bump
Beta version bump 3.4.7
2021-09-19 13:45:48 +02:00
croneter
22f4d22af8
Merge pull request #1629 from croneter/py3-tell-pms
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-19 13:45:39 +02:00
croneter
2843b5da87
Merge pull request #1628 from croneter/py3-stream
Refactor usage of a media part's id
2021-09-19 13:45:30 +02:00
croneter
c17dcc053e Beta version bump 3.4.7 2021-09-19 13:44:51 +02:00
croneter
3480c8fb49 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-19 13:40:16 +02:00
croneter
e65ce668b7 Remove some obsolete comments 2021-09-19 13:37:24 +02:00
croneter
a1f2a676fb Refactor usage of a media part's id 2021-09-13 14:35:58 +02:00
croneter
bce51224f2
Merge pull request #1626 from croneter/py3-streams
Large refactoring of playlist and playqueue code
2021-09-13 13:37:19 +02:00
croneter
b500b7b659 Refactoring: playlist and playqueue items to use API instead of xml 2021-09-13 13:26:04 +02:00
croneter
b4ec68bf82 Refactoring: move all exceptions in a single module 2021-09-13 11:24:06 +02:00
croneter
2c16ce12ae
Merge pull request #1624 from croneter/py3-fix-subs
Transcoding: Fix Plex burning-in subtitles when it should not
2021-09-13 10:12:04 +02:00
croneter
d7b0b670d1 Disentangle and optimize some code
Rename method

Simplify some code

Clarify some code
2021-09-13 10:09:03 +02:00
croneter
727cf98771 Transcoding: Fix Plex burning-in subtitles when it should not 2021-09-13 10:04:29 +02:00
croneter
41f26cdf3b
Merge pull request #1623 from croneter/py3-version-bump
Beta version bump 3.4.6
2021-09-11 16:59:02 +02:00
croneter
b6cc7d0ab1
Merge pull request #1622 from croneter/py3-fix-recursion
Fix RecursionError if a video lies in a root directory
2021-09-11 16:58:49 +02:00
croneter
c4fd45c6bb Beta version bump 3.4.6 2021-09-11 16:54:19 +02:00
croneter
6e5dac46bb Fix RecursionError if videos lie in a root directory 2021-09-11 16:47:28 +02:00
croneter
a019e73dfc
Merge pull request #1618 from croneter/py3-version-bump
Beta version bump 3.4.5
2021-09-09 15:03:57 +02:00
croneter
92a42a4529
Merge pull request #1617 from croneter/py3-reset-resume
Implement "Reset resume position" from the Kodi context menu
2021-09-09 15:03:38 +02:00
croneter
ec8d215a9c Beta version bump 3.4.5 2021-09-09 14:41:22 +02:00
croneter
f1433c3e97 Implement Kodi's "Reset resume position" 2021-09-09 13:55:20 +02:00
croneter
3ac81c7a18
Merge pull request #1612 from croneter/python3-beta
Bump Python3 Master
2021-09-08 11:37:39 +02:00
croneter
f654b700fc
Merge pull request #1611 from croneter/py3-readme
Update readme
2021-09-08 11:36:32 +02:00
croneter
9273c9d78f
Merge pull request #1609 from croneter/py3-version-bump
Stable and beta version bump 3.4.4
2021-09-08 11:34:09 +02:00
croneter
b4c78cc575
Merge pull request #1610 from croneter/py3-nexus
Initial compatibility with 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
2021-09-08 11:33:42 +02:00
croneter
0011a915ba Update readme 2021-09-08 11:31:58 +02:00
croneter
930d930301 Initial compatibility with Kodi 20 Nexus 2021-09-08 11:28:34 +02:00
croneter
87809009f7 Stable and beta version bump 3.4.4 2021-09-08 08:50:12 +02:00
croneter
9d8d95dd8b
Merge pull request #1602 from croneter/py3-version-bump
Beta version bump 3.4.3
2021-09-03 22:32:39 +02:00
croneter
310e829138
Merge pull request #1604 from croneter/py3-artwork
Download landscape artwork from fanart.tv, thanks @geropan
2021-09-03 22:30:56 +02:00
croneter
fee440d8c4 Beta version bump 3.4.3 2021-09-03 22:28:14 +02:00
Antonio Martin
90e9c9c27f Used general term in fanart.tv mapping for prefix completion 2021-09-03 22:25:17 +02:00
Antonio Martin
32dc7697bc Download landscape artwork from fanart.tv 2021-09-03 22:24:59 +02:00
croneter
68cc54c2f4
Merge pull request #1600 from croneter/py3-fix-subtitles
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-03 22:19:33 +02:00
croneter
787b387674
Merge pull request #1603 from croneter/py3-fix-none-subs
Fix PlexKodiConnect setting the Plex subtitle to None
2021-09-03 22:18:04 +02:00
croneter
04da30aaee Merge branch 'py3-fix-none-subs' into python3-beta 2021-09-03 22:13:51 +02:00
croneter
2dd60a4f4a
Merge pull request #1601 from croneter/py3-revert
Revert "Fix PlexKodiConnect changing subtitles for all videos on the PMS"
2021-09-03 22:14:06 +02:00
croneter
789d8459d1 Fix PlexKodiConnect setting the Plex subtitle to None 2021-09-03 22:13:32 +02:00
croneter
30ad8fe880 Fix PlexKodiConnect setting the Plex subtitle to None 2021-09-03 18:29:26 +02:00
croneter
43cc23505a Use Plex settings for audio and subtitle stream selection 2021-09-03 17:54:27 +02:00
croneter
b738688096 Revert "Fix PlexKodiConnect changing subtitles for all videos on the PMS"
This reverts commit 3ef55f5526.
2021-09-03 17:46:39 +02:00
croneter
a38f7ce396
Merge pull request #1594 from croneter/python3-beta
Python3 beta
2021-08-22 20:30:32 +02:00
croneter
6a60329f8a
Merge pull request #1593 from croneter/py3-version-bump
Beta and stable version bump 3.4.2
2021-08-22 20:29:32 +02:00
croneter
a23469c5d6
Merge pull request #1592 from croneter/py3-fix-subtitles
Fix PlexKodiConnect changing or removing subtitles for every video on the PMS
2021-08-22 20:28:33 +02:00
croneter
325b3a7846 Beta and stable version bump 3.4.2 2021-08-22 20:28:25 +02:00
croneter
3ef55f5526 Fix PlexKodiConnect changing subtitles for all videos on the PMS 2021-08-22 18:03:54 +02:00
croneter
eee1902301
Merge pull request #1586 from croneter/python3-beta
Bump Python 3 master
2021-08-15 11:24:55 +02:00
croneter
72d4273f9f
Merge pull request #1585 from croneter/py3-version-bump
Stable and beta version bump 3.4.1
2021-08-15 11:22:27 +02:00
croneter
a2a417f949
Merge pull request #1584 from croneter/py3-fix-playback
Fix PMS setting `List of IP addresses and networks that are allowed without auth` causing Kodi to take forever to start playback
2021-08-15 11:22:01 +02:00
croneter
a0c280f377 Stable and beta version bump 3.4.1 2021-08-15 11:21:09 +02:00
croneter
ab737ca5d0 Addon Paths: Fix Kodi scanning entire video file on playback start 2021-08-15 11:08:21 +02:00
croneter
ab954350b5
Merge pull request #1574 from croneter/python3-beta
Bump Python 3 master
2021-08-01 16:00:57 +02:00
croneter
aae4af6c41
Merge pull request #1573 from croneter/py3-version-bump
Stable and beta version bump 3.4.0
2021-08-01 15:46:15 +02:00
croneter
337b61b20c
Merge pull request #1572 from croneter/py3-logging
Improve logging for converting Unix timestamps
2021-08-01 15:45:58 +02:00
croneter
e835a2d34f
Merge pull request #1565 from croneter/py3-remove-dependency
Remove dependency on script.module.defusedxml - that module is now included in PKC
2021-08-01 15:45:22 +02:00
croneter
c7615049bc Stable and beta version bump 3.4.0 2021-08-01 15:44:26 +02:00
croneter
2a06103eba Improve logging for converting Unix timestamps 2021-08-01 15:41:58 +02:00
croneter
9a779cf116 Remove dependency on script.module.defusedxml - that module is now included in PKC 2021-07-25 15:32:46 +02:00
croneter
2477040375
Merge pull request #1564 from croneter/py3-version-bump
Beta version bump 3.3.5
2021-07-25 15:25:40 +02:00
croneter
b222df108d
Merge pull request #1563 from croneter/py3-fix-context-manager
Fix errors when PKC tries to edit files that don't exist yet
2021-07-25 15:25:25 +02:00
croneter
0835869256
Merge pull request #1562 from croneter/py3-fix-attributeerror
Rewire defusedxml and xml.etree.ElementTree: Fix AttributeError: module 'resources.lib.utils' has no attribute 'ParseError'
2021-07-25 15:25:09 +02:00
croneter
3582043179 Beta version bump 3.3.5 2021-07-25 15:24:01 +02:00
croneter
f2d94d9cb5 Fix AttributeError: module 'resources.lib.utils' has no attribute 'ParseError' 2021-07-25 15:21:54 +02:00
croneter
953b8383bb Fix errors when PKC tries to edit files but they don't exist yet 2021-07-25 15:20:43 +02:00
croneter
6040e40a7a
Merge pull request #1556 from croneter/py3-fix-regression
Fix Regression: AttributeError
2021-07-25 11:04:53 +02:00
croneter
5b32021e26 Fix Regression: AttributeError 2021-07-25 11:03:34 +02:00
croneter
ef324cc615
Merge pull request #1555 from croneter/py3-version-bump
Beta version bump 3.3.4
2021-07-25 10:58:37 +02:00
croneter
6974cff389
Merge pull request #1554 from croneter/py3-fix-recursion
Fix RecursionError: maximum recursion depth exceeded
2021-07-25 10:58:25 +02:00
croneter
cbcc4d1a74
Merge pull request #1553 from croneter/py3-fix-websocket
Bump websocket client: fix AttributeError: 'NoneType' object has no attribute 'is_ssl'
2021-07-25 10:57:42 +02:00
croneter
5bde2c6f98
Merge pull request #1552 from croneter/py3-fix-racing
Fix a racing condition that could lead to the sync getting stuck
2021-07-25 10:57:16 +02:00
croneter
61cd214911 Beta version bump 3.3.4 2021-07-25 10:55:11 +02:00
croneter
b1979262fe Fix RecursionError: maximum recursion depth exceeded 2021-07-25 10:53:22 +02:00
croneter
0cc271031c Bump websocket client: fix AttributeError: 'NoneType' object has no attribute 'is_ssl' 2021-07-25 10:30:27 +02:00
croneter
96e67d31fb Fix a racing condition that could lead to the sync getting stuck 2021-07-25 10:19:20 +02:00
croneter
cf9189380c
Merge pull request #1546 from croneter/py3-version-bump
Beta version bump 3.3.3
2021-07-23 11:04:29 +02:00
croneter
38e4f6e20f
Merge pull request #1545 from croneter/py3-locked-database
Fix likelyhood of `database is locked` error occuring
2021-07-23 11:04:16 +02:00
croneter
a83bac03aa
Merge pull request #1544 from croneter/py3-increase-logging
Fix a racing condition that could lead to the sync process getting stuck
2021-07-23 11:03:59 +02:00
croneter
1711beaf95
Merge pull request #1541 from croneter/py3-fix-attributeerror
Fix AttributeError: module 'urllib' has no attribute 'parse'
2021-07-23 11:03:49 +02:00
croneter
ce72f07fec
Merge pull request #1538 from croneter/py3-anidb
Support for the Plex HAMA agent to let Kodi identify animes (using Kodi's uniqueID 'anidb')
2021-07-23 11:03:35 +02:00
croneter
bb20626c9b
Merge pull request #1537 from croneter/py3-hama
Support forced HAMA IDs when using tvdb uniqueID
2021-07-23 11:03:10 +02:00
croneter
145a592716 Beta version bump 3.3.3 2021-07-23 10:56:52 +02:00
croneter
7c2ad31a21 Fix likelyhood of database is locked error occuring 2021-07-23 10:49:10 +02:00
croneter
143c6271aa Switch to context manager 2021-07-23 10:31:09 +02:00
croneter
07a69a8fa5 Remove obsolete methods 2021-07-23 10:31:09 +02:00
croneter
c664f05718 Fix a racing condition that could lead to the sync getting stuck
Fixup racing
2021-07-23 10:30:51 +02:00
croneter
e36656dc81 Improve logging
fixup logging
2021-07-23 10:30:45 +02:00
croneter
78a3cc434a Fix AttributeError: module 'urllib' has no attribute 'parse' 2021-07-18 14:46:04 +02:00
croneter
cc543d4af3
Merge pull request #1540 from croneter/python3-beta
Bump python3 master
2021-07-18 12:24:56 +02:00
croneter
c9f93b105b
Merge pull request #1539 from croneter/py3-version-bump
Beta version bump 3.3.2
2021-07-18 12:24:05 +02:00
croneter
4ca89490b3 Beta version bump 3.3.2 2021-07-18 12:23:05 +02:00
BrutuZ
423e87046d Support forced HAMA IDs when using tvdb uniqueID 2021-07-18 12:20:57 +02:00
BrutuZ
b8569df1b3 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 12:18:48 +02:00
croneter
9d4bd141a3
Merge pull request #1517 from croneter/py3-version-bump
Beta version bump 3.3.1
2021-06-05 15:45:12 +02:00
croneter
3983011066
Merge pull request #1520 from croneter/py3-update-translations
Update translations from Transifex
2021-06-05 15:44:52 +02:00
croneter
bc35f55ab7
Merge pull request #1519 from croneter/py3-fix-kodi20
Make PKC compatible with Kodi 20 N* by using xbmcvfs for translatePath
2021-06-05 15:44:36 +02:00
croneter
defa43d596
Merge pull request #1515 from croneter/py3-fix-versions
Fix auto-picking of video stream if several video versions are available
2021-06-05 15:44:20 +02:00
croneter
9b9f1cc1a8
Merge pull request #1513 from croneter/py3-fix-continue-watching
Add an additional Plex Hub "PKC Continue Watching" that merges the Plex Continue Watching with On Deck
2021-06-05 15:44:01 +02:00
croneter
4f9c2d88ea Update translations from Transifex 2021-06-05 15:41:53 +02:00
croneter
81076f039e Beta version bump 3.3.1 2021-06-05 15:06:51 +02:00
croneter
321d418a00 Make PKC compatible with Kodi 20 N* 2021-06-05 15:04:57 +02:00
croneter
f3b97e42f8 Fix auto-picking of video stream if several video versions are available 2021-06-05 14:54:13 +02:00
croneter
f53d817908 Add an additional Plex Hub "PKC Continue Watching" that merges the Plex Continue Watching with On Deck 2021-06-05 14:05:20 +02:00
croneter
dcedd252bb
Merge pull request #1508 from croneter/python3-beta
Bump Python 3 master
2021-05-30 10:22:32 +02:00
croneter
a5a587f2b6
Merge pull request #1507 from croneter/py3-version-bump
Stable and beta version bump 3.3.0
2021-05-30 10:21:45 +02:00
croneter
4ac7a52b57 Stable and beta version bump 3.3.0 2021-05-30 10:20:44 +02:00
croneter
cd0815bdab
Merge pull request #1506 from croneter/py3-version-bump
Beta version bump 3.2.4
2021-05-29 17:12:55 +02:00
croneter
adead34f23
Merge pull request #1505 from croneter/py3-fix-websockets
Fix websockets and AttributeError: 'NoneType' object has no attribute
2021-05-29 17:12:41 +02:00
croneter
6f959680ef Beta version bump 3.2.4 2021-05-29 17:11:33 +02:00
croneter
bb3fa955b2 Fix websockets and AttributeError: 'NoneType' object has no attribute 2021-05-29 16:41:19 +02:00
croneter
fea166abf2
Merge pull request #1500 from croneter/py3-version-bump2
Beta version bump 3.2.3
2021-05-26 21:06:43 +02:00
croneter
485dfeceb6
Merge pull request #1499 from croneter/fix-arrow
Get rid of Python arrow; hopefully fix many Python import errors (also occuring in other add-ons!)
2021-05-26 21:06:01 +02:00
croneter
9be5448ee3
Merge pull request #1498 from croneter/py3-fix-websockets
Attempt to fix websocket threading issues and AttributeError: 'NoneType' object has no attribute 'is_ssl' or 'settimeout'
2021-05-26 21:05:49 +02:00
croneter
e2e9aa9f56 Beta version bump 3.2.3 2021-05-26 21:05:42 +02:00
croneter
8d2b3ac1af Get rid of Python arrow; hopefully fix many Python import errors (also occuring in other add-ons!) 2021-05-26 21:00:41 +02:00
croneter
c0ec4cc23e Attempt to fix websocket threading issues and AttributeError: 'NoneType' object has no attribute 'is_ssl' or 'settimeout' 2021-05-26 20:50:55 +02:00
croneter
7787a47026
Merge pull request #1495 from croneter/py3-version-bump
Beta version bump 3.2.2
2021-05-24 20:30:14 +02:00
croneter
6d566c6cd2
Merge pull request #1490 from croneter/py3-update-websockets
Update websocket client to 1.0.0
2021-05-24 20:30:01 +02:00
croneter
9872266c61
Merge pull request #1494 from croneter/py3-fix-typeerror
Fix videos not starting due to a TypeError
2021-05-24 20:29:52 +02:00
croneter
11304c792c
Merge pull request #1492 from croneter/py3-enforce-estuary
Show warning message to remind user to use Estuary for database resets
2021-05-24 20:29:43 +02:00
croneter
fd84960b66 Beta version bump 3.2.2 2021-05-24 20:28:46 +02:00
croneter
7720d3f392 Fix videos not starting due to TypeError 2021-05-24 20:26:25 +02:00
croneter
f5b8084543 Show warning message to remind user to use Estuary for database resets 2021-05-24 15:51:41 +02:00
croneter
6322bfd645 Remove dependency on six 2021-05-24 13:03:34 +02:00
croneter
cb7a5c04e0 Update websocket client to 1.0.0 2021-05-24 13:02:57 +02:00
croneter
edf4369454
Merge pull request #1487 from croneter/py3-version-bump
Beta version bump 3.2.1
2021-05-24 09:43:42 +02:00
croneter
0e1d6c5832
Merge pull request #1486 from croneter/py3-fix-direct-paths
Direct Paths: fix several issues with episodes
2021-05-24 09:43:26 +02:00
croneter
118693c980
Merge pull request #1485 from croneter/py3-fix-widgets
Fix PKC widgets not working at all in some cases
2021-05-24 09:43:16 +02:00
croneter
273f7a79f2 Beta version bump 3.2.1 2021-05-24 09:42:52 +02:00
croneter
bf5591354b Force PKC database reset 2021-05-24 09:39:26 +02:00
croneter
5a0de0e5f7 Fix PKC widgets not working at all in some cases 2021-05-24 09:35:36 +02:00
croneter
cfdcfb4bc4 Direct Paths: fix several issues with episodes 2021-05-23 18:03:18 +02:00
croneter
84d677b7a2
Merge pull request #1482 from croneter/python3-beta
Bump python3 branch
2021-05-23 08:01:40 +02:00
croneter
2cc60a8f70
Merge pull request #1481 from croneter/py3-version-bump
Beta and stable version bump 3.2.0
2021-05-23 08:00:44 +02:00
croneter
a5ab94e6fa Beta and stable version bump 3.2.0 2021-05-23 07:59:48 +02:00
croneter
125ccda075
Merge pull request #1473 from croneter/py3-version-bump
Beta version bump 3.1.4
2021-05-14 08:52:22 +02:00
croneter
f33152049e
Merge pull request #1472 from croneter/py3-fix-attributeerror
Fix AttributeError: module 'shutil' has no attribute 'copy_tree'
2021-05-14 08:52:08 +02:00
croneter
a0a6ef00ec
Merge pull request #1468 from croneter/fix-dict
Fix Alexa and RuntimeError: dictionary keys changed during iteration
2021-05-14 08:51:41 +02:00
croneter
23527814bd Beta version bump 3.1.4 2021-05-14 08:50:51 +02:00
croneter
4f8b4a9f44 Fix AttributeError: module 'shutil' has no attribute 'copy_tree' 2021-05-14 08:46:06 +02:00
croneter
56a74255b6 Fix Alexa and RuntimeError: dictionary keys changed during iteration 2021-05-02 14:35:24 +02:00
croneter
0c5d478974
Merge pull request #1467 from croneter/fix-changelog
Fix changelog
2021-05-02 14:13:28 +02:00
croneter
e787ac06b2 Fix changelog 2021-05-02 14:12:33 +02:00
croneter
81fa71ddb1
Merge pull request #1466 from croneter/fix-regression
Fix regression: fix add-on paths always falling back to direct paths
2021-05-02 14:11:10 +02:00
croneter
7760174900 Fix regression: fix add-on paths always falling back to direct paths 2021-05-02 14:10:33 +02:00
croneter
a6eb60bf1a
Merge pull request #1465 from croneter/version-bump
Beta version bump 3.1.3
2021-05-02 13:50:38 +02:00
croneter
991eaad5df
Merge pull request #1464 from croneter/fix-message
Add PKC setting to disable verification whether we can access a media file
2021-05-02 13:50:14 +02:00
croneter
e6bf68b6f2
Merge pull request #1463 from croneter/adjust-paths
Direct paths: corrections to more closely mirror Kodi's way of saving movie and tv show files to the db
2021-05-02 13:49:53 +02:00
croneter
74a19966d2
Merge pull request #1462 from croneter/fix-playlists
Make sure that the correct file system encoding is used for playlists
2021-05-02 13:49:27 +02:00
croneter
287cb55941
Merge pull request #1461 from croneter/py3-fix-attributerror
Fix a rare AttributeError when using playlists
2021-05-02 13:48:53 +02:00
croneter
ebcc6020d0 Beta version bump 3.1.3 2021-05-02 13:47:36 +02:00
croneter
054332079d Require Kodi database reset for PKC versions < 3.1.3 2021-05-02 13:40:02 +02:00
croneter
a355aee718 Direct paths: set exclude=0 and allAudio=0 in video path table 2021-05-02 13:38:31 +02:00
croneter
f716df0c29 Direct paths: fix pathId in Kodi movie table 2021-05-02 13:38:25 +02:00
croneter
9c8cb61c48 Add PKC setting to disable check whether we can access a media file 2021-05-02 12:55:32 +02:00
croneter
9aa283eea3 Make sure that the correct file system encoding is used for playlists 2021-05-02 12:29:40 +02:00
croneter
f5af67427f Fix a rare AttributeError when using playlists 2021-04-30 10:26:33 +02:00
croneter
8f41b5bf79
Merge pull request #1452 from croneter/version-bump
Beta version bump 3.1.2
2021-04-17 14:20:04 +02:00
croneter
f634554699
Merge pull request #1451 from croneter/fix-companion
Fix PKC not showing up as a casting target in some cases
2021-04-17 14:19:42 +02:00
croneter
7d4a144521
Merge pull request #1450 from croneter/fix-unicode
Fix UnicodeEncodeError if Plex playlist name contains illegal chars
2021-04-17 14:19:23 +02:00
croneter
114377da0f
Merge pull request #1449 from croneter/fix-importerror
Fix ImportError: cannot import name 'dir_util' from 'distutils' on PKC startup
2021-04-17 14:18:56 +02:00
croneter
35c9aeff8b Fix ImportError: cannot import name 'dir_util' from 'distutils' 2021-04-17 14:15:55 +02:00
croneter
8147d8383d Beta version bump 3.1.2 2021-04-17 14:15:06 +02:00
croneter
281fe05158 Fix PKC not showing up as a casting target in some cases 2021-04-17 14:01:56 +02:00
croneter
fbc1ee8985 Fix UnicodeEncodeError if Plex playlist name contains illegal chars 2021-04-17 13:57:23 +02:00
croneter
18599e2e81 Merge branch 'py3-version-bump' into python3-beta 2021-04-05 16:12:47 +02:00
croneter
0de035307d Merge branch 'python3-beta' into py3-version-bump 2021-04-05 16:12:32 +02:00
croneter
2ec5cf026e
Merge pull request #1440 from croneter/fix-direct-paths
Direct paths: fix filename showing instead of full video metadata during playback
2021-04-05 16:11:00 +02:00
croneter
2dd79dbe03
Merge pull request #1424 from croneter/py3-update-translations
Update translations
2021-04-05 16:10:33 +02:00
croneter
8d565bf21f Beta version bump 3.1.1 2021-04-05 16:09:37 +02:00
croneter
0032e6a106 Require Kodi database reset for PKC versions < 3.1.1 2021-04-05 15:27:36 +02:00
croneter
f8f9b98f70 Direct paths: fix movie details not showing on playback 2021-04-05 14:43:17 +02:00
croneter
0ac09dcf59 Update readme 2021-03-20 14:38:19 +01:00
croneter
17ad1de429 Update translations in addon.xml 2021-03-20 14:38:19 +01:00
croneter
fa681c9c0e Update translations from Transifex 2021-03-20 14:38:19 +01:00
croneter
3e84b2b6c2
Merge pull request #1418 from croneter/python3-beta
Bump python 3 master
2021-03-19 18:51:02 +01:00
croneter
4415ad39cf
Merge pull request #1417 from croneter/py3-version-bump
Beta and stable version bump 3.1.0
2021-03-19 18:49:19 +01:00
croneter
63852cdcaf
Merge pull request #1416 from croneter/python3-fix-seek
Fix resume not working if Kodi player start-up is slow
2021-03-19 18:49:02 +01:00
croneter
bc7eb4c85b Beta and stable version bump 3.1.0 2021-03-19 18:47:44 +01:00
croneter
db4f75da0c Fix resume not working if Kodi player start-up is slow 2021-03-19 18:30:24 +01:00
croneter
f0aae64214
Merge pull request #1412 from croneter/py3-version-bump
Beta version bump 3.0.17
2021-03-17 21:43:08 +01:00
croneter
b8c1b514cb Beta version bump 3.0.17 2021-03-17 21:42:42 +01:00
croneter
9151c149e5
Merge pull request #1411 from croneter/fix-socket
Fix error socket.timeout: timed out
2021-03-17 21:41:46 +01:00
croneter
92daa29592
Merge pull request #1410 from croneter/new-websocket
Fix instantaneous background sync and Alexa not working
2021-03-17 21:41:30 +01:00
croneter
6929a4a3a7
Merge pull request #1409 from croneter/fix-runtimeerror
Hopefully fix RuntimeError: no add-on id "plugin.video.plexkodiconnect"
2021-03-17 21:41:17 +01:00
croneter
c4433644ef Adapt websocket client logic 2021-03-17 21:40:52 +01:00
croneter
736f072ccf Fix error socket.timeout: timed out 2021-03-17 21:31:19 +01:00
croneter
8cdb9c999a Hopefully fix RuntimeError: no add-on "plugin.video.plexkodiconnect" 2021-03-17 21:16:11 +01:00
croneter
9866737de3 Add new dependency on script.module.six 2021-03-17 21:11:21 +01:00
croneter
3918810338 Add new Python websocket client 2021-03-14 16:30:52 +01:00
croneter
4c563e7936
Merge pull request #1401 from croneter/py3-version-bump
Beta version bump 3.0.16
2021-03-14 14:12:44 +01:00
croneter
32d3896781 Beta version bump 3.0.16 2021-03-14 14:11:15 +01:00
croneter
35824fe4d0
Merge pull request #1397 from croneter/py3-add-websocket-info
Add information to PKC settings for background sync and Alexa whether a connection has been successfully made
2021-03-14 14:09:38 +01:00
croneter
73bcd85658
Merge pull request #1400 from croneter/python3-beta
Bump Python3 stable
2021-03-14 14:03:12 +01:00
croneter
33483364c5
Merge pull request #1399 from croneter/py3-version-bump
Stable and beta version bump 3.0.15
2021-03-14 14:02:09 +01:00
croneter
ed9a2ca0ac
Merge pull request #1396 from croneter/fix-skip-intro
Rename skip intro skin file
2021-03-14 14:01:57 +01:00
croneter
5780f1b1a1 Stable and beta version bump 3.0.15 2021-03-14 14:00:54 +01:00
croneter
de91987464 Add information to PKC settings about status of websocket and Alexa websocket connections 2021-03-14 13:54:40 +01:00
croneter
c7ff3f573a Rename skip intro skin file 2021-03-14 12:55:51 +01:00
croneter
5e94e008ff
Merge pull request #1387 from croneter/py3-versionbump
Beta version bump 3.0.14 for Kodi Matrix
2021-03-07 17:20:09 +01:00
croneter
61940baaca
Merge pull request #1386 from croneter/py3-fix-icons
Fix PlexKodiConnect Kodi add-on icon and fanart not showing
2021-03-07 17:19:35 +01:00
croneter
ed93771d12
Merge pull request #1384 from croneter/py3-sync-playstates
Quickly sync recently watched items before synching the playstates of the entire Plex library
2021-03-07 17:19:18 +01:00
croneter
ec3616c66a
Merge pull request #1377 from croneter/py3-fix-websocket
Improve logging for websocket JSON loads
2021-03-07 17:18:49 +01:00
croneter
ebda635137
Merge pull request #1375 from croneter/fix-error
Fix TypeError: function missing required argument 'message'
2021-03-07 17:18:22 +01:00
croneter
142c84b501 Beta version bump 3.0.14 for Kodi Matrix 2021-03-07 17:16:01 +01:00
croneter
22d481a806 Fix PlexKodiConnect Kodi add-on icon and fanart not showing 2021-03-07 17:11:01 +01:00
croneter
2dc2b0d99b Sync recently watched items individually before synching every playstate 2021-03-07 15:16:23 +01:00
croneter
dd69928b20 Improve logging for websocket JSON loads 2021-03-01 10:57:59 +01:00
croneter
64ac1f5349 Fix TypeError: function missing required argument 'message' 2021-03-01 10:39:17 +01:00
croneter
47251337f1
Merge pull request #1374 from croneter/py3-version-bump
Version bump 3.0.13
2021-02-26 13:23:58 +01:00
croneter
ca1ca4103d
Merge pull request #1373 from croneter/fix-UnboundLocalError
Fix UnboundLocalError: local variable 'user' referenced before assignment
2021-02-26 13:23:42 +01:00
croneter
54fba4778d Version bump 3.0.13 2021-02-26 13:22:38 +01:00
croneter
c39151e746 Fix UnboundLocalError: local variable 'user' referenced before assignment 2021-02-26 13:08:05 +01:00
croneter
98e6dfc303
Merge pull request #1367 from croneter/py3-version-bump
Version bump 3.0.12
2021-02-24 17:45:36 +01:00
croneter
c49fe06d0f
Merge pull request #1364 from croneter/py3-seasonnames
Sync name and user rating of a TV show season to Kodi
2021-02-24 17:45:12 +01:00
croneter
0ad7f89a62
Merge pull request #1363 from croneter/py3-fix-typeerror
Fix rare TypeError: expected string or buffer on playback start
2021-02-24 17:44:57 +01:00
croneter
6128832140 Version bump 3.0.12 2021-02-24 17:40:13 +01:00
croneter
cd940d60f7 Sync name and user rating of a TV show season to Kodi 2021-02-24 17:20:37 +01:00
croneter
caf8903873 Fix rare TypeError: expected string or buffer on playback start 2021-02-24 15:22:18 +01:00
croneter
2e3a1c311e
Merge pull request #1354 from croneter/version-bump
Version bump 3.0.11
2021-02-21 17:28:16 +01:00
croneter
ac01ff4c60
Merge pull request #1353 from croneter/fix-deletion
Fix TypeError: function missing required argument 'message'
2021-02-21 17:27:56 +01:00
croneter
b1e6ea58a3 Version bump 3.0.11 2021-02-21 17:26:34 +01:00
croneter
8dd533e071 Fix TypeError: function missing required argument 'message' 2021-02-21 17:12:15 +01:00
croneter
97ea2768df
Merge pull request #1344 from croneter/py3-version-bump
Version bump 3.0.10
2021-02-13 18:14:34 +01:00
croneter
3ea4b1fa46
Merge pull request #1340 from croneter/py3-update-translations
Update translations
2021-02-13 18:14:06 +01:00
croneter
61b57540f4 Version bump 3.0.10 2021-02-13 18:12:26 +01:00
croneter
6bf43ea39f
Merge pull request #1342 from croneter/py3-fix-skip-intro
Fix skip intros sometimes not working due to a RuntimeError
2021-02-13 18:10:08 +01:00
croneter
e6e99ba52b Fix skip intros sometimes not working due to a RuntimeError 2021-02-13 18:09:21 +01:00
croneter
b3ba45f900 Update translations 2021-02-13 18:05:41 +01:00
croneter
39a27f750e
Merge pull request #1335 from croneter/version-bump
Version bump 3.0.9
2021-02-09 19:51:29 +01:00
croneter
47550e9367
Merge pull request #1334 from croneter/fix-nextup
Fix Kodi add-on NextUp not working
2021-02-09 19:50:51 +01:00
croneter
9c8ad27f43
Merge pull request #1333 from croneter/skip-intro
Add skip intro functionality
2021-02-09 19:50:32 +01:00
croneter
de2f8941f9 Version bump 3.0.9 2021-02-09 19:36:37 +01:00
croneter
7f8939cee7 Add skip intro functionality 2021-02-09 18:14:31 +01:00
croneter
6719b97d87 Fix Kodi add-on NextUp not working 2021-02-09 18:03:17 +01:00
croneter
f326e49ba7
Merge pull request #1330 from croneter/version-bump
Version bump 3.0.8
2021-02-07 13:12:22 +01:00
croneter
0b577aaabe
Merge pull request #1328 from croneter/fix-keyerror
Fix KeyError: u'game' if Plex Arcade has been activated
2021-02-07 13:11:58 +01:00
croneter
6cea9464c5
Merge pull request #1325 from croneter/fix-attributeerror
Fix AttributeError: 'App' object has no attribute 'threads' when sync is cancelled
2021-02-07 13:11:19 +01:00
croneter
919253dc95 Version bump 3.0.8 2021-02-07 13:10:22 +01:00
croneter
63d7732021 Fix KeyError: u'game' if Plex Arcade has been activated 2021-02-07 13:00:58 +01:00
croneter
d73d0b42d9 Fix AttributeError: 'App' object has no attribute 'threads' when sync is cancelled 2021-02-06 12:20:52 +01:00
croneter
b9f1aefdc3
Merge pull request #1318 from croneter/version-bump
Beta version bump 3.0.7
2021-01-31 17:43:17 +01:00
croneter
0db8ae490c
Merge pull request #1310 from croneter/fix-deadlock
Hopefully fix rare case when sync would get stuck indefinitely
2021-01-31 17:43:00 +01:00
croneter
69d473c92b
Merge pull request #1317 from croneter/fix-valueerror
Fix ValueError: invalid literal for int() for invalid dates sent by Plex
2021-01-31 17:42:49 +01:00
croneter
b9a32d2a3d Beta version bump 3.0.7 2021-01-31 17:41:53 +01:00
croneter
e21f4c143d Hopefully fix rare case when sync would get stuck indefinitely 2021-01-31 17:39:44 +01:00
croneter
82e38366f5 Revert "Add a ton of debug logging for library sync to find deadlock"
This reverts commit 06f7d88d22.
2021-01-31 17:38:37 +01:00
croneter
7465117b00 ValueError: invalid literal for int() for invalid dates sent by Plex 2021-01-31 17:20:17 +01:00
croneter
08cea5b677
Merge pull request #1311 from croneter/version-bump
Beta version bump 3.0.6
2021-01-28 13:06:50 +01:00
croneter
f6e54ac2b6
Merge pull request #1309 from croneter/fix-streams
Fix PKC not auto-picking audio/subtitle stream when transcoding
2021-01-28 13:06:28 +01:00
croneter
a6defcc05a
Merge pull request #1308 from croneter/fix-valueerror
Fix ValueError when deleting a music album
2021-01-28 13:05:59 +01:00
croneter
584bcdbaaf
Merge pull request #1306 from croneter/fix-oserror
Fix OSError: Invalid argument when Plex returns an invalid timestamp
2021-01-28 13:05:31 +01:00
croneter
a261117c70 Beta version bump 3.0.6 2021-01-28 13:04:26 +01:00
croneter
06f7d88d22 Add a ton of debug logging for library sync to find deadlock 2021-01-28 10:02:55 +01:00
croneter
78f1099a4f Fix PKC not auto-picking audio/subtitle stream when transcoding 2021-01-27 16:28:25 +01:00
croneter
5346a1f0a7 Fix ValueError when deleting a music album 2021-01-27 15:45:09 +01:00
croneter
ae3ea91c10 Fix OSError: Invalid argument when Plex returns an invalid timestamp 2021-01-25 13:01:03 +01:00
croneter
21788624b9
Merge pull request #1300 from croneter/fix-migration
Fix sqlite3.OperationalError on PKC upgrade
2021-01-24 17:05:31 +01:00
croneter
7acaafbd69
Merge pull request #1301 from croneter/update-changelog
Update changelog
2021-01-24 17:05:10 +01:00
croneter
d9f022bcd1 Update changelog 2021-01-24 17:04:30 +01:00
croneter
98ac67058e Fix sqlite3.OperationalError on PKC upgrade 2021-01-24 17:02:47 +01:00
croneter
b442a54723
Merge pull request #1299 from croneter/version-bump
Beta version bump 3.0.5 for Kodi Matrix
2021-01-24 16:51:32 +01:00
croneter
43921b1f9b
Merge pull request #1298 from croneter/fix-pix
Fix pictures from Plex picture libraries not working/displaying
2021-01-24 16:51:03 +01:00
croneter
4f307e6eab Beta version bump 3.0.5 for Kodi Matrix 2021-01-24 16:50:07 +01:00
croneter
0e6ca0d290 Add some additional exif picture metadata to listitems. But Kodi skins do not seem to be using that info, unfortunately 2021-01-24 16:35:27 +01:00
croneter
dd70170caa Fix pictures from Plex picture libraries not working/displaying 2021-01-24 15:01:55 +01:00
croneter
9d02e19a68 Optimize capitalization 2021-01-24 14:53:43 +01:00
croneter
e535a8cf70
Merge pull request #1291 from croneter/version-bump
Matrix beta version bump 3.0.4
2021-01-20 17:49:08 +01:00
croneter
068c49683d
Merge pull request #1285 from croneter/add-trailers
Automatically look for missing movie trailers using TMDB
2021-01-20 17:48:53 +01:00
croneter
c5d1927775 Matrix beta version bump 3.0.4 2021-01-20 17:47:55 +01:00
croneter
3825072c5e Alter Plex DB table movie on PKC update to sync TMDB trailers 2021-01-20 17:17:43 +01:00
croneter
c1727e2b5b Look for missing trailers using TMDB 2021-01-20 17:17:43 +01:00
croneter
9226784b2a
Merge pull request #1273 from croneter/version-bump
Version bump 3.0.3
2021-01-09 17:46:33 +01:00
croneter
8fe72d281f
Merge pull request #1265 from Nyaran/feature/py39_thread_is_alive
Change `thread.isAlive` to `thread.is_alive`
2021-01-09 17:46:03 +01:00
croneter
83bb5a54c1
Merge pull request #1272 from croneter/fix-sets
Fix missing Kodi tags for movie collections/sets
2021-01-09 17:45:36 +01:00
croneter
73955357e1
Merge pull request #1271 from croneter/fix-users
Fix PKC suddenly using main Plex user's credentials, e.g. when the PMS address changed
2021-01-09 17:45:20 +01:00
croneter
e8f730ef34 Version bump 3.0.3 2021-01-09 17:44:49 +01:00
croneter
d97a7fdb44 Fix missing Kodi tags for movie collections/sets 2021-01-09 17:31:29 +01:00
croneter
baa2b17615 Fix PKC using main user's token if PMS address changes 2021-01-09 17:25:18 +01:00
croneter
8d34e66764 Improve some code 2021-01-09 17:25:12 +01:00
croneter
a9a4d43cb2 Improve logic when writing credentials to settings file 2021-01-09 17:24:16 +01:00
Nyaran
48034d60ed
Change thread.isAlive to thread.is_alive 2021-01-05 14:11:27 +01:00
croneter
c6291eaba6
Merge pull request #1264 from croneter/version-bump
Version bump 3.0.2
2021-01-03 14:00:00 +01:00
croneter
d2985925c6
Merge pull request #1263 from croneter/fix-attributeerror
Fix AttributeError: module has no attribute try_decode
2021-01-03 13:59:15 +01:00
croneter
b0bc436dbc Version bump 3.0.2 2021-01-03 13:59:06 +01:00
croneter
fd001fa496 Fix AttributeError: module has no attribute try_decode 2021-01-03 13:56:08 +01:00
croneter
cc68bb0c43 Merge branch 'version-bump' into python3 2021-01-02 13:53:04 +01:00
croneter
55de6a1572
Merge pull request #1260 from croneter/merge-from-leia
Merge from Kodi Leia
2021-01-02 13:48:09 +01:00
croneter
48805461d0 Version bump 3.0.1 2021-01-02 13:45:31 +01:00
croneter
189b3ce60c Update changelog 2021-01-02 13:42:29 +01:00
croneter
390a832887 Fix rare KeyError when using PKC widgets 2021-01-02 13:31:50 +01:00
croneter
e2bdfb9c7f Update translations 2021-01-02 13:31:35 +01:00
croneter
110bf3ff9a Stable and beta version bump to 3.0.0 2020-12-27 17:48:49 +01:00
croneter
cb69a2ecbe Update readme 2020-12-27 17:37:59 +01:00
croneter
b7bc919608 Fix docstrings 2020-12-27 17:31:08 +01:00
croneter
a77c6b81f7 Remove some obsolete imports 2020-12-27 17:29:01 +01:00
croneter
6fa19e3495 Remove some obsolete code for Kodi Krypton 2020-12-27 17:22:23 +01:00
croneter
b4a7a9ec41 Fix function arguments 2020-12-27 17:22:23 +01:00
croneter
5e4cfdef52 Fix docstring 2020-12-27 17:22:22 +01:00
croneter
427fc47e7a Remove obsolete imports 2020-12-27 17:22:22 +01:00
croneter
8f5c64b33e Simplify code 2020-12-27 17:22:16 +01:00
croneter
4cf7c753e0 Remove obsolete function in utils.py 2020-12-27 17:22:16 +01:00
croneter
1f2b19ce42 Cleanup save handling of xml.etree.ElementTree 2020-12-27 17:22:15 +01:00
croneter
0dda58ebd3 Fix utils.py imports 2020-12-27 17:22:15 +01:00
croneter
dc2967c8da Add logic to safely enable the Kodi webserver for artwork caching 2020-12-27 17:22:15 +01:00
croneter
3bfe05c5bb Fix suspension of artwork caching 2020-12-27 17:22:15 +01:00
croneter
9c975cfe24 Fix playlist sync: __bool__ returning str instead of bool 2020-12-27 17:22:15 +01:00
croneter
193778f0f4 Fix hashing for playlists 2020-12-27 17:22:15 +01:00
croneter
88a84672c3 Remove encoding and decoding of filepaths 2020-12-27 17:22:14 +01:00
croneter
5c81c15cfd Fix resume not working 2020-12-27 17:22:14 +01:00
croneter
2ef95b1480 Music: include bitrate for songs (channels + sampling rate won't work yet due to Plex not listing this info for an album's children) 2020-12-27 17:22:14 +01:00
croneter
f4923eda22 Music: sync kodi_type for albums additionally 2020-12-27 16:06:17 +01:00
croneter
4da58d72dd Music: do not sync obsolete artwork 2020-12-27 16:06:17 +01:00
croneter
4b9fda9e81 Music: replace songs's year with release date 2020-12-27 16:06:17 +01:00
croneter
71d0cccdaa Fix lookup including year (iyear) 2020-12-27 16:06:17 +01:00
croneter
b5093eb6be Music: replace album's year with release date 2020-12-27 16:06:17 +01:00
croneter
09b2c54675 Remove Music sync code for older Kodi version before Kodi Matrix 2020-12-27 16:06:17 +01:00
croneter
bd8af8652e Music: remove sync of artist fanart 2020-12-27 16:06:16 +01:00
croneter
da66c62f81 Fix check of exception value 2020-12-27 16:06:16 +01:00
croneter
cc20464c15 Fix backgroundthreads and TypeError '<' not supported between instances 2020-12-27 16:06:16 +01:00
croneter
8bdfcbabc8 Fix Plex GDM discovery request 2020-12-27 13:16:45 +01:00
croneter
8ca9613d62 Fix Plex GDM discovery in local LAN 2020-12-27 13:16:45 +01:00
croneter
cc587ed714 Make seek more resilient. Kodi bug still: JSONRPC error Received value does not match any of the union type definitions 2020-12-27 13:16:45 +01:00
croneter
c22b4c782d Remove obsolete encodes and decodes 2020-12-27 13:16:44 +01:00
croneter
ab73d3c1fd Fix playback not starting 2020-12-27 13:16:44 +01:00
croneter
ae949c45ae Some string encoding fixes 2020-12-27 13:16:44 +01:00
croneter
6904494e31 Get rid of utils.try_encode and utils.try_decode 2020-12-27 13:16:44 +01:00
croneter
d306f36869 More string bytes fixes 2020-12-27 13:16:43 +01:00
croneter
d7525274e9 Fix Queue 2020-12-27 13:16:43 +01:00
croneter
382411bff0 Variety of string and bytes fixes 2020-12-27 13:16:43 +01:00
croneter
e32fa567bc Adjust urllib.parse unquote, quote, quote_plus, unquote 2020-12-27 13:16:42 +01:00
croneter
599f134204 Fix encoding for PKC logging 2020-12-19 07:30:40 +01:00
croneter
1d46779d57 Adjust call signature for json.loads 2020-12-19 07:28:31 +01:00
croneter
a56655356c Depricate listitem.setThumbnailImage and listItem.setIconImage 2020-12-19 07:28:10 +01:00
croneter
dcd6756a7d Fix encoding of file paths 2020-12-18 20:01:06 +01:00
croneter
a1f4bc75e6 Update PKC compatibility checks for Kodi 19 2020-12-18 20:00:14 +01:00
croneter
4b4dc1afbf Fix class __repr__ to account for Python 3 2020-12-18 19:59:33 +01:00
croneter
f771b8d3aa Fix logging for Python 3 2020-12-18 19:32:28 +01:00
croneter
ac4b6fc7b5 Fixes to enable an import of the watchdog module 2020-12-18 18:08:39 +01:00
croneter
750cf953da Update Python watchdog module to 1.0.2 2020-12-18 17:50:39 +01:00
croneter
58eaa14043 Clean up imports 2020-12-18 17:43:24 +01:00
croneter
8545f939fe Change all Kodi add-on dependencies to Python 3 2020-12-18 17:42:51 +01:00
croneter
436b1fda83 Automatically convert source code from Python 2 to 3 using futurize 2020-12-18 17:10:20 +01:00
203 changed files with 16574 additions and 5604 deletions

View file

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

View file

@ -1,5 +1,7 @@
[![stable version](https://img.shields.io/badge/stable_version-2.12.3-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.12.5-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 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)
@ -50,10 +52,11 @@ Some people argue that PKC is 'hacky' because of the way it directly accesses th
### PKC Features
- Kodi 19 Matrix is not yet supported (PKC is written in Python 2)
- Support for Kodi 18 Leia
- Support for Kodi 18 Leia and Kodi 19 Matrix
- Preliminary support for Kodi 19 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
- [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/)
- If Plex did not provide a trailer, automatically get one using the Kodi add-on [The Movie Database](https://kodi.wiki/view/Add-on:The_Movie_Database)
- [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
@ -79,6 +82,7 @@ Some people argue that PKC is 'hacky' because of the way it directly accesses th
+ Hungarian, thanks @savage93
+ 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!

983
addon.xml

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,265 @@
version 3.5.8:
- Fix UnboundLocalError: local variable 'identifier' referenced before assignment
- versions 3.5.6-3.5.7 for everyone
version 3.5.7 (beta only):
- Fix Kodi JSON racing condition on playback startup and KeyError
version 3.5.6 (beta only):
- Fix Plex Companion not working by fixing some issues with PKC's http.server's BaseHTTPRequestHandler
version 3.5.5:
- Lost patience with Kodi 19: drop use of Python multiprocessing entirely
version 3.5.4:
- Fix Receiving init() missing 1 required positional argument: certification_country
- Update translations from Transifex
version 3.5.3:
- Add playback settings to let the user choose whether Plex or Kodi provides the default audio or subtitle stream on playback start
version 3.5.2:
- version 3.5.1 for everyone
version 3.5.1 (beta only):
- Refactor stream code and fix Kodi not activating subtitle when it should
- Direct Paths: Fix TypeError: "element indices must be integers" on playback startup
- Android: Fix broken Python multiprocessing module (a Kodi 19.2 bug)
- Fix logging if fanart.tv lookup fails: be less verbose
version 3.5.0:
- versions 3.4.5-3.4.7 for everyone
version 3.4.7 (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
- Large refactoring of playlist and playqueue code
- Refactor usage of a media part's id
version 3.4.6 (beta only):
- Fix RecursionError if a video lies in a root directory
version 3.4.5 (beta only):
- Implement "Reset resume position" from the Kodi context menu
version 3.4.4:
- Initial compatibility with 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
- version 3.4.3 for everyone
version 3.4.3 (beta ony):
- 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 3.4.2:
- Fix PlexKodiConnect changing or removing subtitles for every video on the PMS
version 3.4.1:
- Fix PMS setting `List of IP addresses and networks that are allowed without auth` causing Kodi to take forever to start playback
version 3.4.0:
- Improve logging for converting Unix timestamps
- Remove dependency on script.module.defusedxml - that module is now included in PKC
- version 3.3.3-3.3.5 for everyone
version 3.3.5 (beta only):
- Rewire defusedxml and xml.etree.ElementTree: Fix AttributeError: module 'resources.lib.utils' has no attribute 'ParseError'
- Fix errors when PKC tries to edit files that don't exist yet
version 3.3.4 (beta only):
- Fix a racing condition that could lead to the sync getting stuck
- Fix RecursionError: maximum recursion depth exceeded
- Bump websocket client: fix AttributeError: 'NoneType' object has no attribute 'is_ssl'
version 3.3.3 (beta only):
- Fix a racing condition that could lead to the sync process getting stuck
- Fix likelyhood of `database is locked` error occuring
- Fix AttributeError: module 'urllib' has no attribute 'parse'
- 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 3.3.2:
- version 3.3.1 for everyone
version 3.3.1 (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
- Make PKC compatible with Kodi 20 N* by using xbmcvfs for translatePath
- Update translations
version 3.3.0:
WARNING: Database reset and full resync required
- versions 3.2.1-3.2.4 for everyone
version 3.2.4 (beta only):
- Fix websockets and AttributeError: 'NoneType' object has no attribute
version 3.2.3 (beta only):
- Attempt to fix websocket threading issues and AttributeError: 'NoneType' object has no attribute 'is_ssl' or 'settimeout'
- Get rid of Python arrow; hopefully fix many Python import errors (also occuring in other add-ons!)
version 3.2.2 (beta only):
- Fix videos not starting due to a TypeError
- Show warning message to remind user to use Estuary for database resets
- Update websocket client to 1.0.0
version 3.2.1 (beta only):
WARNING: Database reset and full resync required
- Fix PKC widgets not working at all in some cases
- Direct Paths: fix several issues with episodes
- New Python-dependency: arrow
version 3.2.0:
WARNING: Database reset and full resync required
- version 3.1.1-3.1.4 for everyone
version 3.1.4 (beta only):
- Fix Alexa and RuntimeError: dictionary keys changed during iteration
- Fix AttributeError: module 'shutil' has no attribute 'copy_tree'
version 3.1.3 (beta only):
- Add PKC setting to disable verification whether we can access a media file
- Direct paths: corrections to more closely mirror Kodi's way of saving movie and tv show files to the db
- Make sure that the correct file system encoding is used for playlists
- Fix a rare AttributeError when using playlists
- Fix regression: fix add-on paths always falling back to direct paths
version 3.1.2 (beta only):
- Fix ImportError: cannot import name 'dir_util' from 'distutils' on PKC startup
- Fix UnicodeEncodeError if Plex playlist name contains illegal chars
- Fix PKC not showing up as a casting target in some cases
version 3.1.1 (beta only):
- Direct paths: fix filename showing instead of full video metadata during playback
- Update translations
version 3.1.0:
- version 3.0.16 and 3.0.17 for everyone
- Fix resume not working if Kodi player start-up is slow
version 3.0.17 (beta only):
- Fix instantaneous background sync and Alexa not working
- Hopefully fix RuntimeError: no add-on id "plugin.video.plexkodiconnect"
- Fix error socket.timeout: timed out
version 3.0.16 (beta only):
- Add information to PKC settings for background sync and Alexa whether a connection has been successfully made
version 3.0.15:
- 3.0.14 for everyone
- Rename skip intro skin file
version 3.0.14 (beta only):
- Quickly sync recently watched items before synching the playstates of the entire Plex library
- Fix TypeError: function missing required argument 'message'
- Fix PlexKodiConnect Kodi add-on icon and fanart not showing
- Improve logging for websocket JSON loads
version 3.0.13:
- Fix UnboundLocalError: local variable 'user' referenced before assignment
version 3.0.12:
- Sync name and user rating of a TV show season to Kodi
- Fix rare TypeError: expected string or buffer on playback start
version 3.0.11:
- Fix TypeError: function missing required argument 'message'
version 3.0.10:
- Fix skip intros sometimes not working due to a RuntimeError
- Update translations
version 3.0.9:
- Add skip intro functionality
- Fix Kodi add-on NextUp not working
version 3.0.8:
- Fix KeyError: u'game' if Plex Arcade has been activated
- Fix AttributeError: 'App' object has no attribute 'threads' when sync is cancelled
version 3.0.7:
- Hopefully fix rare case when sync would get stuck indefinitely
- Fix ValueError: invalid literal for int() for invalid dates sent by Plex
version 3.0.6:
- 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 3.0.5:
- Fix pictures from Plex picture libraries not working/displaying
- Fix sqlite3.OperationalError on PKC upgrade
version 3.0.4:
- Automatically look for missing movie trailers using TMDB
version 3.0.3:
- Fix PKC suddenly using main Plex user's credentials, e.g. when the PMS address changed
- Fix missing Kodi tags for movie collections/sets
- Change `thread.isAlive` to `thread.is_alive`
version 3.0.2:
- Fix AttributeError: module has no attribute try_decode
version 3.0.1:
- Fix rare KeyError when using PKC widgets
- Update translations
version 3.0.0:
- Major upgrade from Python 2 to Python 3, allowing use of Kodi 19 Matrix
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 2.12.14 (beta only):
- Add skip intro functionality
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 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 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 2.12.10:
- Fix pictures from Plex picture libraries not working/displaying
version 2.12.9:
- Fix Local variable 'user' referenced before assignement
version 2.12.8:
- version 2.12.7 for everyone
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 2.12.6:
- Fix rare KeyError when using PKC widgets
- Fix suspension of artwork caching and PKC becoming unresponsive
- Update translations
- Versions 2.12.4 and 2.12.5 for everyone
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

View file

@ -1,8 +1,7 @@
# -*- coding: utf-8 -*-
###############################################################################
from __future__ import absolute_import, division, unicode_literals
from sys import listitem
from urllib import urlencode
from urllib.parse import urlencode
from xbmc import getCondVisibility, sleep
from xbmcgui import Window
@ -11,7 +10,7 @@ from xbmcgui import Window
def _get_kodi_type():
kodi_type = listitem.getVideoInfoTag().getMediaType().decode('utf-8')
kodi_type = listitem.getVideoInfoTag().getMediaType()
if not kodi_type:
if getCondVisibility('Container.Content(albums)'):
kodi_type = "album"

View file

@ -1,17 +1,16 @@
# -*- coding: utf-8 -*-
###############################################################################
from __future__ import absolute_import, division, unicode_literals
from builtins import object
import logging
from sys import argv
from urlparse import parse_qsl
from urllib.parse import parse_qsl
import xbmc
import xbmcgui
import xbmcplugin
from resources.lib import entrypoint, utils, transfer, variables as v, loghandler
from resources.lib.tools import unicode_paths
###############################################################################
@ -23,19 +22,15 @@ LOG = logging.getLogger('PLEX.default')
HANDLE = int(argv[1])
class Main():
class Main(object):
# MAIN ENTRY POINT
# @utils.profiling()
def __init__(self):
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')
arguments = argv[2]
path = argv[0]
mode = params.get('mode', '')
itemid = params.get('id', '')

View file

@ -45,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: "
@ -161,6 +168,14 @@ 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"
@ -596,6 +611,11 @@ 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"
@ -660,8 +680,8 @@ 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"
@ -682,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"
@ -950,6 +985,11 @@ 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:"
@ -1133,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"
@ -1143,6 +1188,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Seriály"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1208,6 +1258,31 @@ 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 "

View file

@ -46,6 +46,13 @@ msgstr ""
"Advarsel: Kodi indstillingen \"Afspil næste video automatisk\" er aktiveret."
" Dette kan ødelægge PKC funktionalitet. Deaktiver? "
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 "Brugernavn: "
@ -161,6 +168,14 @@ msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "PKC billede caching er færdiggjort"
# 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 "Portnummer"
@ -596,6 +611,11 @@ msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Vælg Plex biblioteker der skal synkroniseres"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -660,8 +680,8 @@ msgstr "Download film sæt/samling info fra FanArtTV"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Spørg ikke at vælge en bestemt stream/kvalitet"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
@ -682,6 +702,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Transcode billeder"
# 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"
@ -954,6 +989,11 @@ msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr "Escape special characters in path (e.g. space to %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:"
@ -1136,6 +1176,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Nuværende plex.tv status:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1146,6 +1191,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "TV-udsendelser"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1213,6 +1263,31 @@ msgstr ""
"Reload Kodi node filer for alle indstillinger\n"
"nedeunder"
# 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 ud Plex hjemme bruger "

View file

@ -1,6 +1,6 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2020
# Croneter None <croneter@gmail.com>, 2021
#
msgid ""
msgstr ""
@ -8,7 +8,7 @@ msgstr ""
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2020\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"
@ -44,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: "
@ -159,6 +170,17 @@ 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"
@ -602,6 +624,11 @@ 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"
@ -665,8 +692,9 @@ 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"
@ -687,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"
@ -966,6 +1009,11 @@ 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:"
@ -1149,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"
@ -1159,6 +1212,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Serien"
# PKC Settings - Sync
msgctxt "#39075"
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"
msgid ""
@ -1224,6 +1282,31 @@ 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: "

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 ""
@ -151,6 +155,11 @@ 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 ""
@ -567,6 +576,10 @@ msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr ""
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
@ -630,7 +643,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
@ -652,6 +665,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"
@ -1055,6 +1083,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"
@ -1065,6 +1098,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr ""
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
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"
@ -1116,6 +1154,31 @@ 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 ""

View file

@ -1,6 +1,6 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2019
# Croneter None <croneter@gmail.com>, 2020
#
msgid ""
msgstr ""
@ -8,7 +8,7 @@ msgstr ""
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2019\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"
@ -44,6 +44,13 @@ 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: "
msgstr "Usuario: "
@ -164,6 +171,14 @@ 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"
@ -603,6 +618,11 @@ 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"
@ -668,8 +688,8 @@ 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"
@ -690,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"
@ -935,7 +970,7 @@ msgid ""
"Kodi cannot locate the file %s. Please verify your PKC settings. Stop "
"syncing?"
msgstr ""
"Kodi no puede localizer el archive %s. Por favoer verificar sus ajustes de "
"Kodi no puede localizer el archivo %s. Por favor verificar sus ajustes de "
"PKC. ¿Detener la sincronización?"
# Pop-up on initial sync
@ -964,7 +999,12 @@ msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr "Escapar caracteres especiales en la ruta (i.e. espacio a %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"
@ -1149,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"
@ -1159,6 +1204,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Series"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1226,6 +1276,31 @@ 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 "
@ -1430,7 +1505,7 @@ msgid ""
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
"La version actual de Kodi no está soportada por PKC. Por favor consultar el"
"La versión actual de Kodi no está soportada por PKC. Por favor consultar el"
" fórum de Plex."
msgctxt "#39405"
@ -1474,7 +1549,7 @@ msgstr "Sagas"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr "Tablero de PKC (mas rapido)"
msgstr "On Deck de PKC (más rápido)"
msgctxt "#39600"
msgid ""
@ -1616,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

@ -2,6 +2,7 @@
# Translators:
# Dani <danichispa@gmail.com>, 2019
# Bartolome Soriano <bsoriano@gmail.com>, 2019
# Croneter None <croneter@gmail.com>, 2020
#
msgid ""
msgstr ""
@ -9,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Bartolome Soriano <bsoriano@gmail.com>, 2019\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"
@ -45,6 +46,13 @@ 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: "
msgstr "Usuario: "
@ -165,6 +173,14 @@ 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"
@ -604,6 +620,11 @@ 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"
@ -669,8 +690,8 @@ 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"
@ -691,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"
@ -1155,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"
@ -1165,6 +1206,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Series"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1232,6 +1278,31 @@ 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 "

View file

@ -1,6 +1,6 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2019
# Croneter None <croneter@gmail.com>, 2020
#
msgid ""
msgstr ""
@ -8,7 +8,7 @@ msgstr ""
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Croneter None <croneter@gmail.com>, 2019\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"
@ -44,6 +44,13 @@ 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: "
msgstr "Usuario: "
@ -164,6 +171,14 @@ 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"
@ -603,6 +618,11 @@ 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"
@ -668,8 +688,8 @@ 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"
@ -690,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"
@ -935,7 +970,7 @@ msgid ""
"Kodi cannot locate the file %s. Please verify your PKC settings. Stop "
"syncing?"
msgstr ""
"Kodi no puede localizer el archive %s. Por favoer verificar sus ajustes de "
"Kodi no puede localizer el archivo %s. Por favor verificar sus ajustes de "
"PKC. ¿Detener la sincronización?"
# Pop-up on initial sync
@ -964,7 +999,12 @@ msgstr ""
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr "Escapar caracteres especiales en la ruta (i.e. espacio a %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"
@ -1149,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"
@ -1159,6 +1204,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Series"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1226,6 +1276,31 @@ 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 "
@ -1430,7 +1505,7 @@ msgid ""
"The current Kodi version is not supported by PKC. Please consult the Plex "
"forum."
msgstr ""
"La version actual de Kodi no está soportada por PKC. Por favor consultar el"
"La versión actual de Kodi no está soportada por PKC. Por favor consultar el"
" fórum de Plex."
msgctxt "#39405"
@ -1474,7 +1549,7 @@ msgstr "Sagas"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr "Tablero de PKC (mas rapido)"
msgstr "On Deck de PKC (más rápido)"
msgctxt "#39600"
msgid ""
@ -1616,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

@ -46,6 +46,13 @@ msgstr ""
"Avertissement : Le paramètre Kodi \"Lecture automatique de la vidéo "
"suivante\" est activé. Cela pourrait casser PKC. Désactiver ?"
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 "Identifiant : "
@ -165,6 +172,14 @@ msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "Mise en cache de images pour PKC-seulement terminée"
# 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 "Numéro de port"
@ -608,6 +623,11 @@ msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Sélectionner les bibliothèques Plex à synchroniser"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -673,8 +693,8 @@ msgstr "Télécharger les affiches des sagas sur FanArtTV"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Ne pas demander de choisir un certain flux/qualité"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
@ -695,6 +715,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Forcer le transcodage des images"
# 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"
@ -975,6 +1010,11 @@ msgstr ""
"Echapper les caractères spéciaux dans le chemin (ex: %20 au lieu des "
"espaces)"
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr "Caractères sûrs pour les urls http(s), dav(s) et (s)ftp"
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -1163,6 +1203,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "État actuel de plex.tv: "
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1173,6 +1218,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Séries TV"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1240,6 +1290,31 @@ msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
"Recharger les fichiers de Kodi pour appliquer tous les paramètres ci-dessous"
# 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 User "

View file

@ -50,6 +50,13 @@ msgstr ""
"Avertissement : Le paramètre Kodi \"Lecture automatique de la vidéo "
"suivante\" est activé. Cela pourrait casser PKC. Désactiver ?"
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 "Identifiant : "
@ -169,6 +176,14 @@ msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "Mise en cache de images pour PKC-seulement terminée"
# 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 "Numéro de port"
@ -612,6 +627,11 @@ msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Sélectionner les bibliothèques Plex à synchroniser"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -677,8 +697,8 @@ msgstr "Télécharger les affiches des sagas sur FanArtTV"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Ne pas demander de choisir un certain flux/qualité"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
@ -699,6 +719,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Forcer le transcodage des images"
# 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"
@ -979,6 +1014,11 @@ msgstr ""
"Echapper les caractères spéciaux dans le chemin (ex: %20 au lieu des "
"espaces)"
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr "Caractères sûrs pour les urls http(s), dav(s) et (s)ftp"
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -1167,6 +1207,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "État actuel de plex.tv: "
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1177,6 +1222,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Séries TV"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1244,6 +1294,31 @@ msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
"Recharger les fichiers de Kodi pour appliquer tous les paramètres ci-dessous"
# 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 User "

View file

@ -1,7 +1,7 @@
# XBMC Media Center language file
# Translators:
# Croneter None <croneter@gmail.com>, 2019
# Savage93 <savageistheking@gmail.com>, 2020
# Savage93 <savageistheking@gmail.com>, 2021
#
msgid ""
msgstr ""
@ -9,7 +9,7 @@ msgstr ""
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Savage93 <savageistheking@gmail.com>, 2020\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"
@ -45,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: "
@ -162,6 +173,14 @@ 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"
@ -606,6 +625,11 @@ 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"
@ -670,8 +694,10 @@ 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"
@ -692,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"
@ -968,6 +1009,11 @@ 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:"
@ -1151,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"
@ -1161,6 +1212,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "TV sorozatok"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1229,6 +1285,31 @@ 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: "

View file

@ -47,6 +47,13 @@ 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 "Nome utente:"
@ -164,6 +171,14 @@ 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"
@ -603,6 +618,11 @@ 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"
@ -668,8 +688,8 @@ 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"
@ -690,6 +710,21 @@ 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"
@ -967,6 +1002,11 @@ 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"
msgid "Original Plex MOVIE path to replace:"
@ -1152,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"
@ -1162,6 +1207,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Serie TV"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1229,6 +1279,31 @@ 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 "

File diff suppressed because it is too large Load diff

View file

@ -45,6 +45,13 @@ msgstr ""
"Įspėjimas: „Kodi“ nustatymas „Leisti kitą vaizdo įrašą automatiškai“ yra "
"įjungtas. Tai gali pažeisti „PKC“. Išjungti?"
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 "Vartotojo vardas:"
@ -164,6 +171,14 @@ msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "„PKC“-baigtas tik atvaizdžių podėliavimas"
# 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 "Prievado numeris"
@ -600,6 +615,11 @@ msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Pasirinkti sinchronizuojamas „Plex“ bibliotekas"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -664,8 +684,8 @@ msgstr "Atsisiųskite filmų komplekto / rinkinio iliustraciją iš „FanArtTV
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Neprašykite pasirinkti tam tikro srauto / kokybės"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
@ -686,6 +706,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Priverstinai perkoduoti nuotraukas"
# 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"
@ -963,6 +998,11 @@ msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr "Kaita specialių simbolių kelyje (pvz., tarpas %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:"
@ -1145,6 +1185,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Dabartinė „plex.tv“ būsena:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1155,6 +1200,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "TV Laidos"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1222,6 +1272,31 @@ msgstr ""
"Atnaujinkite „Kodi“ mazgų failus, kad galėtumėte taikyti visus toliau "
"pateiktus nustatymus"
# 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 "Atjungti „Plex“ namų vartotoją"

View file

@ -44,6 +44,13 @@ msgstr ""
"Brīdinājums: Kodi iestatījums \"Atskaņot nākamo video automātiski\" ir "
"ieslēgts. Tas var salauzt PKC. Izslēgt?"
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 "Lietotājvārds:"
@ -159,6 +166,14 @@ msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "Tikai-PKC attēlu kešošana pabeigta"
# 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 Numurs"
@ -593,6 +608,11 @@ msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Izvēlies kuras Plex bibliotēkas sinhronizēt"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -657,8 +677,8 @@ msgstr "Lejupielādēt filmu komplektu/kolekciju attēlus no FanArtTV"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Nejautāt par konkrētas kvalitātes/straumes izvēli"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
@ -679,6 +699,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Uzspiest attēlu pārkodēšanu"
# 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"
@ -946,6 +981,11 @@ 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:"
@ -1100,7 +1140,7 @@ msgstr "Uzspiest atjaunošanu Kodi ādiņai apturot atskaņošanu"
# PKC Settings - Appearance Tweaks
msgctxt "#39066"
msgid "Recently Added: Also show already watched movies"
msgstr ""
msgstr "Nesen Pievienots: Rādīt arī jau skatītas filmas"
# PKC Settings - Connection
msgctxt "#39067"
@ -1110,7 +1150,7 @@ msgstr "Tavs pašreizējais Plex Media Serveris:"
# PKC Settings - Connection
msgctxt "#39068"
msgid "Manually enter Plex Media Server address"
msgstr ""
msgstr "Pats ievadi Plex Media Server adresi"
# PKC Settings - Connection
msgctxt "#39069"
@ -1127,6 +1167,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Pašreizējais plex.tv statuss:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1137,6 +1182,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Seriāli"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1173,28 +1223,53 @@ msgstr ""
# Button text for choosing PKC mode
msgctxt "#39081"
msgid "Add-on Paths"
msgstr ""
msgstr "Spraudņu Ceļš"
# Button text for choosing PKC mode
msgctxt "#39082"
msgid "Direct Paths"
msgstr ""
msgstr "Tiešie Ceļi"
# Dialog for manually entering PMS
msgctxt "#39083"
msgid "Enter PMS IP or URL"
msgstr ""
msgstr "Ievadi PMS IP vai URL"
# Dialog for manually entering PMS
msgctxt "#39084"
msgid "Enter PMS port"
msgstr ""
msgstr "Ievadi PMS portu"
# 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 ""
@ -1291,23 +1366,23 @@ msgstr "Tikai trūkstošo"
# 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 "Nav pieteicies 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 "Pieteicies plex.tv"
# Message in the PKC settings to display the plex.tv username
msgctxt "#39228"
msgid "Plex admin user"
msgstr ""
msgstr "Plex admin user"
# 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 ""
msgstr "Lietotāja pieteikšanās plex.tv neizdevās"
# Message in the PKC settings to display the plex.tv username
msgctxt "#39230"
@ -1472,7 +1547,7 @@ msgstr ""
# Addon Disclaimer
msgctxt "#39705"
msgid "Use at your own risk"
msgstr ""
msgstr "Lieto uz savu atbildību"
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
@ -1530,7 +1605,7 @@ msgstr "Sinhronizēt"
# Shown during sync process
msgctxt "#39715"
msgid "Synching playlists"
msgstr ""
msgstr "Sinhronizē spēļsarakstus"
# 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

@ -48,6 +48,13 @@ msgstr ""
"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: "
msgstr "Gebruikersnaam: "
@ -163,6 +170,14 @@ 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"
@ -601,6 +616,11 @@ 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"
@ -665,8 +685,8 @@ 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"
@ -687,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"
@ -957,6 +992,11 @@ 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:"
@ -1139,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"
@ -1149,6 +1194,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "TV series"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1217,6 +1267,31 @@ 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 "

View file

@ -46,6 +46,13 @@ msgstr ""
"Advarsel: Kodi instilling \"Automatisk avspilling av neste video\" er "
"aktivert. Det kan medføre problemer med PKC. Ønsker du å deaktivere?"
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 "Brukernavn:"
@ -165,6 +172,14 @@ msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "PKC mellomlagring av bilder gjennomført"
# 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 "Portnummer"
@ -600,6 +615,11 @@ msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Velg Plex bibliotek som skal synkroniseres"
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
@ -662,8 +682,8 @@ msgstr "Last ned filmsamling-kunst fra FanArtTV"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Ikke spør om å velge en utvalgt strøm/kvalitet"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
@ -684,6 +704,21 @@ msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "Tving transkoding av bilde"
# 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"
@ -951,6 +986,11 @@ msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr "Unngå spesielle tegn i stier (eksempel mellomrom til %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:"
@ -1132,6 +1172,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Aktuell plex.tv statys:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1142,6 +1187,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "TV-show"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1207,6 +1257,31 @@ 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 "Logg av Plex Home User"

File diff suppressed because it is too large Load diff

View file

@ -45,6 +45,13 @@ 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: "
msgstr "Utilizador: "
@ -162,6 +169,14 @@ 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"
@ -590,6 +605,11 @@ 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"
@ -652,8 +672,8 @@ 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"
@ -674,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"
@ -945,6 +980,11 @@ 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:"
@ -1128,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"
@ -1138,6 +1183,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Programas de TV"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1200,6 +1250,31 @@ 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"

View file

@ -44,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: "
@ -163,6 +170,14 @@ 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"
@ -593,6 +608,11 @@ 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"
@ -655,8 +675,8 @@ 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"
@ -677,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"
@ -948,6 +983,11 @@ 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:"
@ -1131,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"
@ -1141,6 +1186,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Programas de TV"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1203,6 +1253,31 @@ 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"

View file

@ -49,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 "Имя пользователя: "
@ -166,6 +173,14 @@ 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 "Порт"
@ -605,6 +620,11 @@ 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"
@ -669,8 +689,8 @@ 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"
@ -691,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"
@ -961,6 +996,11 @@ 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:"
@ -1144,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"
@ -1154,6 +1199,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Сериалы"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1219,6 +1269,31 @@ 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"

View file

@ -6,6 +6,7 @@
# Samuel Linde <samuel@linde.im>, 2018
# Nisse Karlsson <transifex@xcorp.at>, 2019
# Ludwig Johnson <public@ludwigjohnson.se>, 2019
# namob <boman.d@gmail.com>, 2021
#
msgid ""
msgstr ""
@ -13,7 +14,7 @@ msgstr ""
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
"Last-Translator: Ludwig Johnson <public@ludwigjohnson.se>, 2019\n"
"Last-Translator: namob <boman.d@gmail.com>, 2021\n"
"Language-Team: Swedish (Sweden) (https://www.transifex.com/croneter/teams/73837/sv_SE/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -49,6 +50,13 @@ msgstr ""
"Varning: Kodi-inställningen \"Spela nästa video automatiskt\" är aktiverad. "
"Detta kan orsaka problem med PKC. Vill du avaktivera?"
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 "Användarnamn:"
@ -165,6 +173,14 @@ msgctxt "#30028"
msgid "PKC-only image caching completed"
msgstr "Cachelagring av PKC-bilder färdig"
# 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 "Portnummer"
@ -266,7 +282,7 @@ msgstr "Videokvalitet då omkodning krävs"
msgctxt "#30161"
msgid "Auto-adjust transcoding quality (deactivate for Chromecast)"
msgstr ""
msgstr "Justera automatiskt omkodningskvaliteten (inaktivera för Chromecast)"
msgctxt "#30165"
msgid "Direct Play"
@ -553,12 +569,12 @@ msgstr ""
# PKC Settings - Sync Options
msgctxt "#30515"
msgid "Maximum items to request from the server at once"
msgstr "max antal föremåls begäran till server"
msgstr "Max antal föremål att fråga efter på en och samma gång"
# PKC Settings, category name
msgctxt "#30516"
msgid "Playback"
msgstr "uppspelning"
msgstr "Uppspelning"
# PKC Settings - Connection
msgctxt "#30517"
@ -578,7 +594,7 @@ msgstr "Fråga om uppspelning av trailers."
# PKC Settings - Plex
msgctxt "#30520"
msgid "Skip PMS delete confirmation (use at your own risk)"
msgstr "Skippa PMS radera konfirmations meddelande (avnänd på egen risk)"
msgstr "Hoppa över PMS bekräftelse på att radera data (använd på egen risk)"
# PKC Settings - Playback
msgctxt "#30521"
@ -588,7 +604,7 @@ msgstr "Spola tillbaka vid återuppta(i sekunder)"
# PKC Settings - Playback
msgctxt "#30522"
msgid "Force transcode h265/HEVC"
msgstr "Tvinga omkodning (trancoding) av h265/hevc"
msgstr "Tvinga omkodning av H.265/HEVC"
# PKC Settings - Sync Options
msgctxt "#30523"
@ -600,31 +616,36 @@ msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr "Välj Plex-bibliotek att synkronisera "
# PKC Settings - Playback
msgctxt "#30525"
msgid "Skip intro"
msgstr ""
# PKC Settings - Playback
msgctxt "#30527"
msgid "Ignore specials in next episodes"
msgstr "ignorera specialer av nästa episoder"
msgstr "ignorera specialer i nästa episoder"
msgctxt "#30528"
msgid "Permanent users to add to the session"
msgstr "Permanenta användare tillägs till denna session"
msgstr "Permanenta användare att lägga till i sessionen"
# PKC Settings - Advanced
msgctxt "#30529"
msgid "Startup delay (in seconds)"
msgstr "Uppstartnings dröjsmål (i sekunder)"
msgstr "Fördröjning vid uppstart (i sekunder)"
msgctxt "#30531"
msgid "Enable new content notification"
msgstr "Aktivera nytt innehål notifiering"
msgstr "Aktivera notifiering vid nytt innehåll"
msgctxt "#30532"
msgid "Duration of the video library pop up (in seconds)"
msgstr "varaktighet av video biblioteks pop up(i sekunder)"
msgstr "Varaktighet av videobibliotekspopup (i sekunder)"
msgctxt "#30533"
msgid "Duration of the music library pop up (in seconds)"
msgstr "varaktighet av musik biblioteks pop up(i sekunder)"
msgstr "Varaktighet av musikbibliotekspopup (i sekunder)"
msgctxt "#30534"
msgid "Server messages"
@ -634,12 +655,12 @@ msgstr "Server meddelanden"
msgctxt "#30535"
msgid "Generate a new unique Plex device Id (e.g. to clone Kodi)"
msgstr ""
"Generera ett nytt unikt Plex enhets id (för att exempelvis klona Kodi)"
"Generera ett nytt unikt Plex enhets-id (för att exempelvis klona Kodi)"
# PKC Settings - Connection
msgctxt "#30536"
msgid "Users must log in every time Kodi restarts"
msgstr "användare måste logga in varje gång kodi startas om"
msgstr "Användare måste logga in varje gång Kodi startas om"
# PKC Settings warning
msgctxt "#30537"
@ -654,7 +675,7 @@ msgstr "Fullständig återställning av Kodi-databasen krävs, se \"Avancerad\""
# PKC Settings - Artwork
msgctxt "#30539"
msgid "Download additional art from FanArtTV"
msgstr "ladda ner extra affischer från FanArtTV"
msgstr "Ladda ner extra affischer från FanArtTV"
# PKC Settings - Artwork
msgctxt "#30540"
@ -663,8 +684,8 @@ msgstr "Ladda ner film set affischer från FanArtTV"
# PKC Settings - Playback
msgctxt "#30541"
msgid "Don't ask to pick a certain stream/quality"
msgstr "Fråga inte om välja stream kvalitet"
msgid "Transcoding: Auto-pick audio and subtitle stream using Plex defaults"
msgstr ""
# PKC Settings - Playback
msgctxt "#30542"
@ -678,12 +699,27 @@ msgstr "Föredra Kodi-bilder för kollektioner"
msgctxt "#30544"
msgid "Artwork"
msgstr "affischer"
msgstr "Affischer"
# PKC Settings - Playback
msgctxt "#30545"
msgid "Force transcode pictures"
msgstr "tvinga omkodning(transcoding) av bilder"
msgstr "Tvinga omkodning av bilder"
# 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"
@ -708,18 +744,18 @@ msgstr "Server är online"
# Plex notification when we need to transcode
msgctxt "#33004"
msgid "PMS enforced transcoding"
msgstr ""
msgstr "PMS-tvingad omkodning"
# Plex notification when we need to use direct streaming (instead of
# transcoding)
msgctxt "#33005"
msgid "PMS enforced direct streaming"
msgstr ""
msgstr "PMS-tvingad direkt ström"
# Error notification
msgctxt "#33009"
msgid "Invalid username or password"
msgstr "fel användarnamn eller lösenord"
msgstr "Fel användarnamn eller lösenord"
msgctxt "#33010"
msgid "User is unauthorized for server {0}"
@ -732,7 +768,7 @@ msgstr "Plex.tv skickade inte en lista över giltiga Plex-användare."
# Dialog before playback
msgctxt "#33013"
msgid "Choose the audio stream"
msgstr "välj ljudfil"
msgstr "Välj ljudström"
# Dialog before playback
msgctxt "#33014"
@ -742,19 +778,20 @@ msgstr "Välj undertext"
# Dialog before playback
msgctxt "#33016"
msgid "Play trailers?"
msgstr "spela upp trailer?"
msgstr "Spela upp trailer?"
# Error message
msgctxt "#33032"
msgid ""
"Failed to generate a new device Id. See your logs for more information."
msgstr ""
"misslyckades med att generera nytt enhets id. kolla logs för mer information"
"Misslyckades med att generera nytt enhets-id. Kontrollera loggar för mer "
"information."
# Pop-up informing about Kodi restart
msgctxt "#33033"
msgid "Kodi will now restart to apply the changes."
msgstr "Kodi kommer startas om för att applicera inställningar"
msgstr "Kodi kommer startas om för att applicera förändringarna"
# Confirmation dialog before item gets deleted from the PMS
msgctxt "#33041"
@ -762,22 +799,22 @@ msgid ""
"Delete file(s) from Plex Server? This will also delete the file(s) from "
"disk!"
msgstr ""
"radera filer från plex server?filer kommer också raderas från hårddisk"
"Radera fil(er) från Plexserver? Fil(er) kommer också raderas från hårddisk!"
# PKC Settings - Playback
msgctxt "#39000"
msgid "- Number of trailers to play before a movie"
msgstr "-antal trailers att spela för filmen"
msgstr "- Antal trailers att spela innan en film"
# PKC Settings - Playback
msgctxt "#39001"
msgid "Boost audio when transcoding"
msgstr "öka ljudet när det omkodas(transcoding)"
msgstr "Öka ljudet när det omkodas"
# PKC Settings - Playback
msgctxt "#39002"
msgid "Burnt-in subtitle size"
msgstr "inbränd undertext storlek"
msgstr "Storlek på inbränd undertext"
# PKC Settings - Sync
msgctxt "#39003"
@ -787,7 +824,7 @@ msgstr "Antal samtidiga nedladdningstrådar"
# PKC Settings - Plex
msgctxt "#39004"
msgid "Enable Plex Companion (restart Kodi!)"
msgstr "aktivera Plex Companion (restart kodi)"
msgstr "Aktivera Plex Companion (kräver omstart av Kodi!)"
# PKC Settings - Plex
msgctxt "#39005"
@ -797,37 +834,37 @@ msgstr "Plex Companion Port(ändra bara om det är nödvändigt)"
# PKC Settings - Plex
msgctxt "#39008"
msgid "Plex Companion: Allows flinging media to Kodi through Plex"
msgstr "Plex companion: tillåt strömmning av media till kodi från plex."
msgstr "Plex Companion: tillåt strömmning av media till Kodi från Plex."
# Error message
msgctxt "#39009"
msgid "Could not login to plex.tv. Please try signing in again."
msgstr "kunde inte logga in tillplex.tv. Försök logga in igen."
msgstr "Kunde inte logga in till plex.tv. Försök logga in igen."
# Error message
msgctxt "#39010"
msgid "Problems connecting to plex.tv. Network or internet issue?"
msgstr "problem att ansluta till plex.tv. nätverk eller interna fel."
msgstr "Problem att ansluta till plex.tv. Nätverks- eller internetproblem?"
# Error message
msgctxt "#39011"
msgid "Could not find any Plex server in the network. Aborting..."
msgstr "kunde inte hitta plex server på nätverket. avbryter."
msgstr "Kunde inte hitta Plex-server på nätverket. Avbryter..."
# Dialog text for choosing PMS
msgctxt "#39012"
msgid "Choose your Plex server"
msgstr "välj din plex server."
msgstr "Välj din Plex-server"
# Error message
msgctxt "#39013"
msgid "Not yet authorized for Plex server "
msgstr "inte authoriserad ännu"
msgstr "Ännu inte auktoriserad för Plex-servern"
# Error message
msgctxt "#39014"
msgid "Please sign in to plex.tv."
msgstr "logga in på plex.tv"
msgstr "Vänligen logga in mot plex.tv"
# Error message
msgctxt "#39015"
@ -840,8 +877,9 @@ msgid ""
"Disable Plex music library? (It is HIGHLY recommended to use Plex music only"
" with direct paths for large music libraries. Kodi might crash otherwise)"
msgstr ""
"avaktivera Plex music bibliotek (rekommenderat att endast använda plex musik"
" med direkt paths till stora musik bibliotek.)"
"Inaktivera Plex musikbibliotek? (Det är STARKT rekommenderat att endast "
"använda Direct Path tillsammans med stora musikbibliotek, Kodi kan krasha "
"annars)"
# Pop-up on initial sync
msgctxt "#39017"
@ -875,17 +913,17 @@ msgstr "lokal"
# Error message
msgctxt "#39023"
msgid "Failed to authenticate. Did you login to plex.tv?"
msgstr "misslyckade att authentisera. Har du loggat in på plex.tv"
msgstr "Misslyckades med autentisering. Har du loggat in på plex.tv?"
# PKC Settings - Plex
msgctxt "#39025"
msgid "Automatically log into plex.tv on startup"
msgstr "automatiskt logga in på plex.tv vid start"
msgstr "Logga in automatiskt på plex.tv vid start"
# PKC Settings - Sync
msgctxt "#39026"
msgid "Enable constant background sync"
msgstr "aktivera konstant bakgrunds synkronisering"
msgstr "Aktivera konstant bakgrundssynkronisering"
# Pop-up on initial sync
msgctxt "#39028"
@ -895,14 +933,15 @@ msgid ""
"shares need to use direct paths (e.g. smb://myNAS/mymovie.mkv or "
"\\\\myNAS/mymovie.mkv)!"
msgstr ""
"VARNING! om du väljer native läge, kanske du förlorar tillgång till vissa plex funktioner som t.ex.\n"
"plex trailer och omkodning(transcoding) alternativ. alla plex shares behöver använda direct paths\n"
"(t.ex. smb://myNAS/mymovie.mkv or \\\\myNAS/mymovie.mkv)!"
"VARNING! Om du väljer \"Nativt\" läge kan du förlora tillgång till vissa "
"Plex-funktioner såsom Plextrailer och omkodningsalternativ. ALLA "
"Plexutdelningar måste använda Direct Path (t.ex smb://myNAS/mymovie.mkv "
"eller \\\\myNAS\\mymovie.mkv)!"
# Pop-up on initial sync
msgctxt "#39029"
msgid "Network credentials"
msgstr "nätverks inloggningsuppgifter"
msgstr "Inloggningsuppgifter för nätverk"
# Pop-up on initial sync
msgctxt "#39030"
@ -921,7 +960,7 @@ msgid ""
"Kodi cannot locate the file %s. Please verify your PKC settings. Stop "
"syncing?"
msgstr ""
"Kodi kan inte hitta filen 1%s. verifiera pkc inställningar. sluta synka?"
"Kodi kan inte hitta filen %s. Verifiera PKC-inställningar. Avsluta synk?"
# Pop-up on initial sync
msgctxt "#39033"
@ -929,13 +968,13 @@ msgid ""
"Transform Plex UNC library paths \\\\myNas\\mymovie.mkv automatically to smb"
" paths, smb://myNas/mymovie.mkv? (recommended)"
msgstr ""
"omvandla plex unc biblioteks paths \\\\myNas\\mymovie.mkv automatiskt till smb delningar.\n"
"Omvandla Plex UNC-sökvägar \\\\myNas\\mymovie.mkv automatiskt till SMB-sökvägar \n"
"smb://myNas/mymovie.mkv? (rekommenderas)"
# PKC Settings - Customize Paths
msgctxt "#39034"
msgid "Replace Plex UNC paths \\\\myNas with smb://myNas"
msgstr "Ersätt Plex UNC sökväg \\\\myNas med smb://myNas"
msgstr "Ersätt Plex UNC-sökväg \\\\myNas med smb://myNas"
# PKC Settings - Customize Paths
msgctxt "#39035"
@ -943,18 +982,23 @@ msgid ""
"Replace Plex paths /volume1/media or \\\\myserver\\media with custom SMB "
"paths smb://NAS/mystuff"
msgstr ""
"Ersätt Plex sökväg /volume1/media eller \\\\myserver\\media med anpassade "
"SMB sökvägar smb://NAS/mystuff"
"Ersätt Plex-sökväg /volume1/media eller \\\\myserver\\media med anpassade "
"SMB-sökvägar smb://NAS/mystuff"
# PKC Settings - Customize Paths
msgctxt "#39036"
msgid "Escape special characters in path (e.g. space to %20)"
msgstr "Omkoda specialtecken i sökväg (exempelvis mellanslag som %20)"
# PKC Settings - Customize Paths
msgctxt "#39090"
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
msgstr "Säkra karaktärer för http(s), dav(s) och (s)ftp URLer"
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
msgstr "Ursprunglig Plex MOVIE sökväg att ersätta."
msgstr "Ursprunglig Plex MOVIE-sökväg att ersätta:"
# PKC Settings - Customize Paths
msgctxt "#39038"
@ -964,7 +1008,7 @@ msgstr "Ersätt Plex MOVIE med:"
# PKC Settings - Customize Paths
msgctxt "#39039"
msgid "Original Plex TV SHOWS path to replace:"
msgstr "Ursprunglig Plex TV SHOWS sökväg att ersätta."
msgstr "Ursprunglig Plex TV SHOWS-sökväg att ersätta:"
# PKC Settings - Customize Paths
msgctxt "#39040"
@ -974,7 +1018,7 @@ msgstr "Ersätt Plex TV SHOWS med:"
# PKC Settings - Customize Paths
msgctxt "#39041"
msgid "Original Plex MUSIC path to replace:"
msgstr "Ursprunglig Plex MUSIC sökväg att ersätta."
msgstr "Ursprunglig Plex MUSIC-sökväg att ersätta:"
# PKC Settings - Customize Paths
msgctxt "#39042"
@ -997,8 +1041,8 @@ msgid ""
"Please enter your custom smb paths in the settings under \"Sync Options\" "
"and then restart Kodi"
msgstr ""
"Ange din anpassade smb-sökväg i inställningarna under \"Synkroniserings "
"inställningar\" och starta sedan om Kodi"
"Ange din anpassade SMB-sökväg i inställningarna under "
"\"Synkroniseringsinställningar\" och starta om Kodi"
# PKC Settings - Customize Paths
msgctxt "#39045"
@ -1033,12 +1077,12 @@ msgstr "Välj en Plex Server från en lista"
# PKC Settings - Sync
msgctxt "#39051"
msgid "Wait before sync new/changed PMS item [s]"
msgstr "Vänta före synkronisering av nya/ändrade PMS objekt"
msgstr "Vänta före synkronisering av nya/ändrade PMS-objekt"
# PKC Settings - Sync
msgctxt "#39052"
msgid "Background Sync"
msgstr "Bakgrundssynkning."
msgstr "Bakgrundssynkronisering."
# PKC Settings - Sync
msgctxt "#39053"
@ -1060,6 +1104,8 @@ msgctxt "#39056"
msgid ""
"Used by sync and when attempting Direct Paths. Restart Kodi on changes!"
msgstr ""
"Används av sync samt vid användning av Direct Paths. Starta om Kodi vid "
"förändringar!"
# PKC Settings, category name
msgctxt "#39057"
@ -1133,6 +1179,11 @@ msgctxt "#39071"
msgid "Current plex.tv status:"
msgstr "Nuvarande plex.tv status:"
# PKC Settings - Connection
msgctxt "#39072"
msgid "Background sync connection:"
msgstr ""
# PKC Settings, category name
msgctxt "#39073"
msgid "Appearance Tweaks"
@ -1143,6 +1194,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "TV-Serier"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1160,7 +1216,7 @@ msgstr "Maximalt antal filmer att visa i widgets"
# PKC Settings - Plex
msgctxt "#39078"
msgid "Plex Companion Update Port (change only if needed)"
msgstr "Plex Companion Update port (ändra bara vid behov)"
msgstr "Plex Companion Update-port (ändra bara vid behov)"
# Error message
msgctxt "#39079"
@ -1168,7 +1224,7 @@ msgid ""
"Plex Companion could not open the GDM port. Please change it in the PKC "
"settings."
msgstr ""
"Plex Companion kunde inte öppna GDM porten. Ändra den i PKC inställningarna."
"Plex Companion kunde inte öppna GDM-porten. Ändra den i PKC-inställningarna."
# Pop-up on initial sync.
# Check that next translations for Add-on Paths and Direct Paths are
@ -1205,7 +1261,32 @@ msgstr "Ange PMS port"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr "Ladda om Kodi nodfiler för att applicera alla inställningar nedan"
msgstr "Ladda om Kodi-nodfiler för att applicera alla inställningar nedan"
# 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 "
@ -1223,8 +1304,8 @@ msgstr "Utför manuell bibliotekssynkronisering"
msgctxt "#39205"
msgid "Unable to run the sync, the add-on is not connected to a Plex server."
msgstr ""
"Kunde inte köra synkronisering, tillägget är inte ansluten till en Plex "
"server."
"Kunde inte köra synkronisering, tillägget är inte ansluten till en "
"Plexserver."
msgctxt "#39206"
msgid ""
@ -1267,7 +1348,7 @@ msgstr "Ange din Plex Media Server IP eller URL, exempelvis:"
msgctxt "#39217"
msgid "Use HTTPS (SSL) connections? Answer should probably be yes."
msgstr ""
msgstr "Används HTTPS(SSL)-anslutningar? Svaret bör nog vara ja."
msgctxt "#39218"
msgid "Error contacting PMS"
@ -1403,7 +1484,7 @@ msgstr ""
msgctxt "#39402"
msgid " may not work correctly until the database is reset."
msgstr "fungerar kanske inte fören databasen är återställd. "
msgstr "fungerar kanske inte förn databasen är återställd. "
msgctxt "#39403"
msgid ""
@ -1516,7 +1597,7 @@ msgstr "Använd på egen risk"
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "Don't burn-in any subtitle"
msgstr ""
msgstr "Använd inte några inbrända undertexter"
# If user gets prompted to choose between several audio/subtitle tracks and
# language is unknown
@ -1556,7 +1637,7 @@ msgstr ""
# Shown during sync process
msgctxt "#39712"
msgid "downloaded"
msgstr "Nedladdade"
msgstr "nedladdade"
# Shown during sync process
msgctxt "#39713"

View file

@ -44,6 +44,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 "Ім'я користувача:"
@ -160,6 +167,14 @@ 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 "Номер порту"
@ -600,6 +615,11 @@ 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"
@ -664,8 +684,8 @@ msgstr "Завантажувати матеріали набору фільмі
# 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"
@ -686,6 +706,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"
@ -957,6 +992,11 @@ 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 "Безпечні символи для URL-адрес http(s), dav(s) та (s)ftp"
# PKC Settings - Customize Paths
msgctxt "#39037"
msgid "Original Plex MOVIE path to replace:"
@ -1141,6 +1181,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"
@ -1151,6 +1196,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "Серіали"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1218,6 +1268,31 @@ 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 Home"
@ -1528,7 +1603,7 @@ msgstr "Використовуйте на свій ризик"
# If user gets prompted to choose between several subtitles to burn in
msgctxt "#39706"
msgid "Don't burn-in any subtitle"
msgstr ""
msgstr "Не виводити жодних субтитрів"
# If user gets prompted to choose between several audio/subtitle tracks and
# language is unknown

View file

@ -44,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 "用户名 "
@ -159,6 +166,14 @@ 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 "端口号"
@ -585,6 +600,11 @@ 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"
@ -647,8 +667,8 @@ 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"
@ -669,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"
@ -918,6 +953,11 @@ 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:"
@ -1097,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"
@ -1107,6 +1152,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "电视节目"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1165,6 +1215,31 @@ 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家庭用户 "

View file

@ -42,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 "使用者: "
@ -157,6 +164,14 @@ 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 "埠號"
@ -583,6 +598,11 @@ 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"
@ -645,8 +665,8 @@ 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"
@ -667,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"
@ -916,6 +951,11 @@ 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:"
@ -1093,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"
@ -1103,6 +1148,11 @@ msgctxt "#39074"
msgid "TV Shows"
msgstr "電視節目"
# PKC Settings - Sync
msgctxt "#39075"
msgid "Verify access to media files while synching"
msgstr ""
# Pop-up during initial sync
msgctxt "#39076"
msgid ""
@ -1161,6 +1211,31 @@ 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用戶 "

View file

@ -4,7 +4,6 @@
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

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .. import utils
@ -15,6 +14,8 @@ class Account(object):
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

View file

@ -1,8 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import Queue
import queue
from threading import Lock, RLock
import xbmc
@ -19,6 +18,8 @@ class App(object):
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:
@ -38,19 +39,19 @@ class App(object):
self.lock_playlists = Lock()
# Plex Companion Queue()
self.companion_queue = Queue.Queue(maxsize=100)
self.companion_queue = queue.Queue(maxsize=100)
# Websocket_client queue to communicate with librarysync
self.websocket_queue = Queue.Queue()
self.websocket_queue = queue.Queue()
# xbmc.Monitor() instance from kodimonitor.py
self.monitor = None
# xbmc.Player() instance
self.player = None
# All thread instances
self.threads = []
# Instance of FanartThread()
self.fanart_thread = None
# Instance of MetadataThread()
self.metadata_thread = None
# Instance of ImageCachingThread()
self.caching_thread = None
# Dialog to skip intro
self.skip_intro_dialog = None
@property
def is_playing(self):
@ -60,24 +61,24 @@ class App(object):
def is_playing_video(self):
return self.player.isPlayingVideo() == 1
def register_fanart_thread(self, thread):
self.fanart_thread = thread
def register_metadata_thread(self, thread):
self.metadata_thread = thread
self.threads.append(thread)
def deregister_fanart_thread(self, thread):
self.fanart_thread.unblock_callers()
self.fanart_thread = None
def deregister_metadata_thread(self, thread):
self.metadata_thread.unblock_callers()
self.metadata_thread = None
self.threads.remove(thread)
def suspend_fanart_thread(self, block=True):
def suspend_metadata_thread(self, block=True):
try:
self.fanart_thread.suspend(block=block)
self.metadata_thread.suspend(block=block)
except AttributeError:
pass
def resume_fanart_thread(self):
def resume_metadata_thread(self):
try:
self.fanart_thread.resume()
self.metadata_thread.resume()
except AttributeError:
pass

View file

@ -1,9 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import secrets
from .. import utils, json_rpc as js, variables as v
from .. import utils, json_rpc as js
LOG = getLogger('PLEX.connection')
@ -38,22 +38,36 @@ class Connection(object):
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
if not utils.settings('enableTextureCache') == 'true':
LOG.info('Artwork caching disabled')
return
self.webserver_password = js.get_setting('services.webserverpassword')
if not self.webserver_password:
LOG.warn('No password set for the Kodi web server. Generating a '
'new random password')
self.webserver_password = secrets.token_urlsafe(16)
js.set_setting('services.webserverpassword', self.webserver_password)
if not js.get_setting('services.webserver'):
# 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.
utils.messageDialog(utils.lang(29999), utils.lang(30004))
# Enable the webserver, it is disabled. Will force a Kodi pop-up
js.set_setting('services.webserver', True)
if not js.get_setting('services.webserver'):
LOG.warn('User chose to not enable Kodi webserver')
utils.settings('enableTextureCache', value='false')
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
self.verify_ssl_cert = None
# 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
@ -74,8 +88,7 @@ class Connection(object):
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.verify_ssl_cert = None
self.ssl_cert_path = utils.settings('sslcert') \
if utils.settings('sslcert') != 'None' else None
self.https = utils.settings('https') == 'true'

View file

@ -1,7 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from .. import utils
@ -57,8 +55,6 @@ class Sync(object):
# How often shall we sync?
self.full_sync_intervall = None
# Background Sync disabled?
self.background_sync_disabled = None
# How long shall we wait with synching a new item to make sure Plex got all
# metadata?
self.backgroundsync_saftymargin = None
@ -73,6 +69,8 @@ class Sync(object):
self.run_lib_scan = None
# Set if user decided to cancel sync
self.stop_sync = False
# Do we check whether we can access a media file?
self.check_media_file_existence = False
# Could we access the paths?
self.path_verified = False
@ -81,7 +79,6 @@ class Sync(object):
# 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.enable_alexa = None
self.load()
@ -97,6 +94,8 @@ class Sync(object):
def load(self):
self.direct_paths = utils.settings('useDirectPaths') == '1'
self.check_media_file_existence = \
utils.settings('check_media_file_existence') == '1'
self.enable_music = utils.settings('enableMusic') == 'true'
self.artwork = utils.settings('usePlexArtwork') == 'true'
self.replace_smb_path = utils.settings('replaceSMB') == 'true'
@ -110,7 +109,7 @@ class Sync(object):
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.escape_path_safe_chars = utils.settings('escapePathSafeChars')
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'
@ -122,8 +121,6 @@ class Sync(object):
Any settings unrelated to syncs to the Kodi database - can thus be
safely reset without a Kodi reboot
"""
self.background_sync_disabled = utils.settings('enableBackgroundSync') == 'false'
self.enable_alexa = utils.settings('enable_alexa') == 'true'
self.sync_dialog = utils.settings('dbSyncIndicator') == 'true'
self.full_sync_intervall = int(utils.settings('fullSyncInterval')) * 60
self.backgroundsync_saftymargin = int(utils.settings('backgroundsync_saftyMargin'))

View file

@ -1,8 +1,5 @@
#!/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 = {
@ -36,7 +33,8 @@ class PlayState(object):
'muted': False,
'playmethod': None,
'playcount': None,
'external_player': False # bool - xbmc.Player().isExternalPlayer()
'external_player': False, # bool - xbmc.Player().isExternalPlayer()
'intro_markers': [],
}
def __init__(self):

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import requests
@ -82,7 +81,7 @@ class ImageCachingThread(backgroundthread.KillableThread):
for url in self._url_generator(kind, kodi_type):
if self.should_suspend() or self.should_cancel():
return False
cache_url(url)
cache_url(url, self.should_suspend)
# Toggles Image caching completed to Yes
utils.settings('plex_status_image_caching', value=utils.lang(107))
return True
@ -95,16 +94,13 @@ class ImageCachingThread(backgroundthread.KillableThread):
break
def cache_url(url):
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),
url=f'http://{app.CONN.webserver_username}:{app.CONN.webserver_password}@{app.CONN.webserver_host}:{app.CONN.webserver_port}/image/image://{url}',
auth=(app.CONN.webserver_username,
app.CONN.webserver_password),
timeout=TIMEOUT)
@ -113,11 +109,11 @@ def cache_url(url):
# download. All is well
break
except requests.ConnectionError:
if app.APP.stop_pkc:
# Kodi terminated
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))

View file

@ -1,12 +1,12 @@
#!/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 queue
import heapq
from collections import deque
from functools import total_ordering
from . import utils, app, variables as v
@ -113,7 +113,7 @@ class KillableThread(threading.Thread):
self._suspension_reached.set()
class ProcessingQueue(Queue.Queue, object):
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
@ -135,38 +135,6 @@ class ProcessingQueue(Queue.Queue, object):
def _qsize(self):
return self._current_queue._qsize() if self._current_queue else 0
def _total_qsize(self):
return sum(q._qsize() for q in self._queues) if self._queues else 0
def put(self, item, block=True, timeout=None):
"""
PKC customization of Queue.put. item needs to be the tuple
(count [int], {'section': [Section], 'xml': [etree xml]})
"""
self.not_full.acquire()
try:
if self.maxsize > 0:
if not block:
if self._total_qsize() == self.maxsize:
raise Queue.Full
elif timeout is None:
while self._total_qsize() == self.maxsize:
self.not_full.wait()
elif timeout < 0:
raise ValueError("'timeout' must be a non-negative number")
else:
endtime = _time() + timeout
while self._total_qsize() == self.maxsize:
remaining = endtime - _time()
if remaining <= 0.0:
raise Queue.Full
self.not_full.wait(remaining)
self._put(item)
self.unfinished_tasks += 1
self.not_empty.notify()
finally:
self.not_full.release()
def _put(self, item):
for i, section in enumerate(self._sections):
if item[1]['section'] == section:
@ -183,16 +151,13 @@ class ProcessingQueue(Queue.Queue, object):
Once the get()-method returns None, you've received the sentinel and
you've thus exhausted the queue
"""
self.not_full.acquire()
try:
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()
finally:
self.not_full.release()
def add_section(self, section):
"""
@ -202,17 +167,32 @@ class ProcessingQueue(Queue.Queue, object):
Be sure to set section.number_of_items correctly as it will signal
when processing is completely done for a specific section!
"""
self.mutex.acquire()
try:
with self.mutex:
self._add_section(section)
finally:
self.mutex.release()
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())
else queue.Queue())
if self._current_section is None:
self._activate_next_section()
@ -237,7 +217,7 @@ class ProcessingQueue(Queue.Queue, object):
return item[1]
class OrderedQueue(Queue.PriorityQueue, object):
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
@ -253,15 +233,15 @@ class OrderedQueue(Queue.PriorityQueue, object):
self.next_index = 0
super(OrderedQueue, self).__init__(maxsize)
def _qsize(self, len=len):
def _qsize(self):
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):
def _get(self):
self.next_index += 1
return heappop(self.queue)
return heapq.heappop(self.queue)
class Tasks(list):
@ -280,14 +260,20 @@ class Tasks(list):
self.pop().cancel()
@total_ordering
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 __lt__(self, other):
"""Magic method Task<Other Task; compares the tasks' priorities."""
return self.priority - other.priority > 0
def __eq__(self, other):
"""Magic method Task=Other Task; compares the tasks' priorities."""
return self.priority == other.priority
def start(self):
BGThreader.addTask(self)
@ -328,10 +314,10 @@ class FunctionAsTask(Task):
self._callback(result)
class MutablePriorityQueue(Queue.PriorityQueue):
def _get(self, heappop=heapq.heappop):
class MutablePriorityQueue(queue.PriorityQueue):
def _get(self):
self.queue.sort()
return heappop(self.queue)
return heapq.heappop(self.queue)
def lowest(self):
"""Return the lowest priority item in the queue (not reliable!)."""
@ -371,7 +357,7 @@ class BackgroundWorker(object):
return self._abort or app.APP.monitor.abortRequested()
def start(self):
if self._thread and self._thread.isAlive():
if self._thread and self._thread.is_alive():
return
self._thread = KillableThread(target=self._queueLoop, name='BACKGROUND-WORKER({0})'.format(self.name))
@ -388,7 +374,7 @@ class BackgroundWorker(object):
self._runTask(self._task)
self._queue.task_done()
self._task = None
except Queue.Empty:
except queue.Empty:
LOG.debug('(%s): Idle', self.name)
def shutdown(self, block=True):
@ -397,13 +383,13 @@ class BackgroundWorker(object):
if self._task:
self._task.cancel()
if block and self._thread and self._thread.isAlive():
if block and self._thread and self._thread.is_alive():
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()
return self._thread and self._thread.is_alive()
class NonstoppingBackgroundWorker(BackgroundWorker):
@ -428,7 +414,7 @@ class NonstoppingBackgroundWorker(BackgroundWorker):
return self._working
class BackgroundThreader:
class BackgroundThreader(object):
def __init__(self, name=None, worker=BackgroundWorker, worker_count=6):
self.name = name
self._queue = MutablePriorityQueue()
@ -508,7 +494,7 @@ class BackgroundThreader:
qitem.priority = lowest - 1
class ThreaderManager:
class ThreaderManager(object):
def __init__(self,
worker=NonstoppingBackgroundWorker,
worker_count=WORKER_COUNT):

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import xbmc
@ -30,7 +29,6 @@ def getXArgsDeviceInfo(options=None, include_token=True):
"""
xargs = {
'Accept': '*/*',
'Connection': 'keep-alive',
"Content-Type": "application/x-www-form-urlencoded",
# "Access-Control-Allow-Origin": "*",
'Accept-Language': xbmc.getLanguage(xbmc.ISO_639_1),
@ -43,6 +41,8 @@ def getXArgsDeviceInfo(options=None, include_token=True):
'X-Plex-Version': v.ADDON_VERSION,
'X-Plex-Client-Identifier': getDeviceId(),
'X-Plex-Provides': 'client,controller,player,pubsub-player',
'X-Plex-Protocol': '1.0',
'Cache-Control': 'no-cache'
}
if include_token and utils.window('pms_token'):
xargs['X-Plex-Token'] = utils.window('pms_token')

View file

@ -3,7 +3,6 @@
"""
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
@ -28,7 +27,7 @@ def skip_to(params):
LOG.debug('Skipping to playQueueItemID %s, plex_id %s',
playqueue_item_id, plex_id)
found = True
for player in js.get_players().values():
for player in list(js.get_players().values()):
playqueue = PQ.PLAYQUEUES[player['playerid']]
for i, item in enumerate(playqueue.items):
if item.id == playqueue_item_id:
@ -49,7 +48,7 @@ def convert_alexa_to_companion(dictionary):
"""
The params passed by Alexa must first be converted to Companion talk
"""
for key in dictionary:
for key in list(dictionary):
if key in v.ALEXA_TO_COMPANION:
dictionary[v.ALEXA_TO_COMPANION[key]] = dictionary[key]
del dictionary[key]
@ -86,7 +85,7 @@ def process_command(request_path, params):
elif request_path == "player/playback/stop":
js.stop()
elif request_path == "player/playback/seekTo":
js.seek_to(int(params.get('offset', 0)))
js.seek_to(float(params.get('offset', 0.0)) / 1000.0)
elif request_path == "player/playback/stepForward":
js.smallforward()
elif request_path == "player/playback/stepBack":

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import xbmcgui
@ -60,14 +59,14 @@ 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().decode('utf-8')
self.selected_option = option.getLabel()
LOG.info('option selected: %s', self.selected_option)
self.close()
def _add_editcontrol(self, x, y, height, width, password=None):
media = path_ops.path.join(
v.ADDON_PATH, 'resources', 'skins', 'default', 'media')
filename = utils.try_encode(path_ops.path.join(media, 'white.png'))
filename = path_ops.path.join(media, 'white.png')
control = xbmcgui.ControlImage(0, 0, 0, 0,
filename=filename,
aspectRatio=0,

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import xbmc
import xbmcgui
@ -92,7 +91,7 @@ class ContextMenu(object):
options.append(OPTIONS['Addon'])
context_menu = context.ContextMenu(
"script-plex-context.xml",
utils.try_encode(v.ADDON_PATH),
v.ADDON_PATH,
"default",
"1080i")
context_menu.set_options(options)
@ -126,13 +125,13 @@ class ContextMenu(object):
"""
delete = True
if utils.settings('skipContextMenu') != "true":
if not utils.dialog("yesno", heading="{plex}", line1=utils.lang(33041)):
if not utils.dialog("yesno", heading="{plex}", message=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:
utils.dialog("ok", heading="{plex}", line1=utils.lang(30414))
utils.dialog("ok", heading="{plex}", message=utils.lang(30414))
def _PMS_play(self):
"""
@ -143,8 +142,8 @@ class ContextMenu(object):
playqueue.clear()
app.PLAYSTATE.context_menu_play = True
handle = self.api.fullpath(force_addon=True)[0]
handle = 'RunPlugin(%s)' % handle
xbmc.executebuiltin(handle.encode('utf-8'))
handle = f'RunPlugin({handle})'
xbmc.executebuiltin(handle)
def _extras(self):
"""

View file

@ -4,18 +4,13 @@ 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
class LockedDatabase(Exception):
"""
Dedicated class to make sure we're not silently catching locked DBs.
"""
pass
def catch_operationalerrors(method):
"""
sqlite.OperationalError is raised immediately if another DB connection
@ -32,7 +27,7 @@ def catch_operationalerrors(method):
try:
return method(self, *args, **kwargs)
except sqlite3.OperationalError as err:
if 'database is locked' not in err:
if err.args[0] and 'database is locked' not in err.args[0]:
# Not an error we want to catch, so reraise it
raise
attempts -= 1
@ -43,7 +38,7 @@ def catch_operationalerrors(method):
self.kodiconn.commit()
if self.artconn:
self.artconn.commit()
if app.APP.monitor.waitForAbort(0.1):
if app.APP.monitor.waitForAbort(DB_WRITE_ATTEMPTS_TIMEOUT):
# PKC needs to quit
return
# Start new transactions

View file

@ -1,39 +0,0 @@
#!/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

@ -0,0 +1,188 @@
# defusedxml
#
# Copyright (c) 2013-2020 by Christian Heimes <christian@python.org>
# Licensed to PSF under a Contributor Agreement.
# See https://www.python.org/psf/license for licensing details.
"""Defused xml.etree.ElementTree facade
"""
from __future__ import print_function, absolute_import
import sys
import warnings
from xml.etree.ElementTree import ParseError
from xml.etree.ElementTree import TreeBuilder as _TreeBuilder
from xml.etree.ElementTree import parse as _parse
from xml.etree.ElementTree import tostring
import importlib
from .common import DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden
__origin__ = "xml.etree.ElementTree"
def _get_py3_cls():
"""Python 3.3 hides the pure Python code but defusedxml requires it.
The code is based on test.support.import_fresh_module().
"""
pymodname = "xml.etree.ElementTree"
cmodname = "_elementtree"
pymod = sys.modules.pop(pymodname, None)
cmod = sys.modules.pop(cmodname, None)
sys.modules[cmodname] = None
try:
pure_pymod = importlib.import_module(pymodname)
finally:
# restore module
sys.modules[pymodname] = pymod
if cmod is not None:
sys.modules[cmodname] = cmod
else:
sys.modules.pop(cmodname, None)
# restore attribute on original package
etree_pkg = sys.modules["xml.etree"]
if pymod is not None:
etree_pkg.ElementTree = pymod
elif hasattr(etree_pkg, "ElementTree"):
del etree_pkg.ElementTree
_XMLParser = pure_pymod.XMLParser
_iterparse = pure_pymod.iterparse
# patch pure module to use ParseError from C extension
pure_pymod.ParseError = ParseError
return _XMLParser, _iterparse
_XMLParser, _iterparse = _get_py3_cls()
_sentinel = object()
class DefusedXMLParser(_XMLParser):
def __init__(
self,
html=_sentinel,
target=None,
encoding=None,
forbid_dtd=False,
forbid_entities=True,
forbid_external=True,
):
super().__init__(target=target, encoding=encoding)
if html is not _sentinel:
# the 'html' argument has been deprecated and ignored in all
# supported versions of Python. Python 3.8 finally removed it.
if html:
raise TypeError("'html=True' is no longer supported.")
else:
warnings.warn(
"'html' keyword argument is no longer supported. Pass "
"in arguments as keyword arguments.",
category=DeprecationWarning,
)
self.forbid_dtd = forbid_dtd
self.forbid_entities = forbid_entities
self.forbid_external = forbid_external
parser = self.parser
if self.forbid_dtd:
parser.StartDoctypeDeclHandler = self.defused_start_doctype_decl
if self.forbid_entities:
parser.EntityDeclHandler = self.defused_entity_decl
parser.UnparsedEntityDeclHandler = self.defused_unparsed_entity_decl
if self.forbid_external:
parser.ExternalEntityRefHandler = self.defused_external_entity_ref_handler
def defused_start_doctype_decl(self, name, sysid, pubid, has_internal_subset):
raise DTDForbidden(name, sysid, pubid)
def defused_entity_decl(
self, name, is_parameter_entity, value, base, sysid, pubid, notation_name
):
raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name)
def defused_unparsed_entity_decl(self, name, base, sysid, pubid, notation_name):
# expat 1.2
raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) # pragma: no cover
def defused_external_entity_ref_handler(self, context, base, sysid, pubid):
raise ExternalReferenceForbidden(context, base, sysid, pubid)
# aliases
# XMLParse is a typo, keep it for backwards compatibility
XMLTreeBuilder = XMLParse = XMLParser = DefusedXMLParser
def parse(source, parser=None, forbid_dtd=False, forbid_entities=True, forbid_external=True):
if parser is None:
parser = DefusedXMLParser(
target=_TreeBuilder(),
forbid_dtd=forbid_dtd,
forbid_entities=forbid_entities,
forbid_external=forbid_external,
)
return _parse(source, parser)
def iterparse(
source,
events=None,
parser=None,
forbid_dtd=False,
forbid_entities=True,
forbid_external=True,
):
if parser is None:
parser = DefusedXMLParser(
target=_TreeBuilder(),
forbid_dtd=forbid_dtd,
forbid_entities=forbid_entities,
forbid_external=forbid_external,
)
return _iterparse(source, events, parser)
def fromstring(text, forbid_dtd=False, forbid_entities=True, forbid_external=True):
parser = DefusedXMLParser(
target=_TreeBuilder(),
forbid_dtd=forbid_dtd,
forbid_entities=forbid_entities,
forbid_external=forbid_external,
)
parser.feed(text)
return parser.close()
XML = fromstring
def fromstringlist(sequence, forbid_dtd=False, forbid_entities=True, forbid_external=True):
parser = DefusedXMLParser(
target=_TreeBuilder(),
forbid_dtd=forbid_dtd,
forbid_entities=forbid_entities,
forbid_external=forbid_external,
)
for text in sequence:
parser.feed(text)
return parser.close()
__all__ = [
"ParseError",
"XML",
"XMLParse",
"XMLParser",
"XMLTreeBuilder",
"fromstring",
"fromstringlist",
"iterparse",
"parse",
"tostring",
]

View file

@ -0,0 +1,67 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <christian@python.org>
# Licensed to PSF under a Contributor Agreement.
# See https://www.python.org/psf/license for licensing details.
"""Defuse XML bomb denial of service vulnerabilities
"""
from __future__ import print_function, absolute_import
import warnings
from .common import (
DefusedXmlException,
DTDForbidden,
EntitiesForbidden,
ExternalReferenceForbidden,
NotSupportedError,
_apply_defusing,
)
def defuse_stdlib():
"""Monkey patch and defuse all stdlib packages
:warning: The monkey patch is an EXPERIMETNAL feature.
"""
defused = {}
with warnings.catch_warnings():
from . import cElementTree
from . import ElementTree
from . import minidom
from . import pulldom
from . import sax
from . import expatbuilder
from . import expatreader
from . import xmlrpc
xmlrpc.monkey_patch()
defused[xmlrpc] = None
defused_mods = [
cElementTree,
ElementTree,
minidom,
pulldom,
sax,
expatbuilder,
expatreader,
]
for defused_mod in defused_mods:
stdlib_mod = _apply_defusing(defused_mod)
defused[defused_mod] = stdlib_mod
return defused
__version__ = "0.8.0.dev1"
__all__ = [
"DefusedXmlException",
"DTDForbidden",
"EntitiesForbidden",
"ExternalReferenceForbidden",
"NotSupportedError",
]

View file

@ -0,0 +1,47 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <christian@python.org>
# Licensed to PSF under a Contributor Agreement.
# See https://www.python.org/psf/license for licensing details.
"""Defused xml.etree.cElementTree
"""
import warnings
# This module is an alias for ElementTree just like xml.etree.cElementTree
from .ElementTree import (
XML,
XMLParse,
XMLParser,
XMLTreeBuilder,
fromstring,
fromstringlist,
iterparse,
parse,
tostring,
DefusedXMLParser,
ParseError,
)
__origin__ = "xml.etree.cElementTree"
warnings.warn(
"defusedxml.cElementTree is deprecated, import from defusedxml.ElementTree instead.",
category=DeprecationWarning,
stacklevel=2,
)
__all__ = [
"ParseError",
"XML",
"XMLParse",
"XMLParser",
"XMLTreeBuilder",
"fromstring",
"fromstringlist",
"iterparse",
"parse",
"tostring",
# backwards compatibility
"DefusedXMLParser",
]

View file

@ -0,0 +1,85 @@
# defusedxml
#
# Copyright (c) 2013-2020 by Christian Heimes <christian@python.org>
# Licensed to PSF under a Contributor Agreement.
# See https://www.python.org/psf/license for licensing details.
"""Common constants, exceptions and helpe functions
"""
import sys
import xml.parsers.expat
PY3 = True
# Fail early when pyexpat is not installed correctly
if not hasattr(xml.parsers.expat, "ParserCreate"):
raise ImportError("pyexpat") # pragma: no cover
class DefusedXmlException(ValueError):
"""Base exception"""
def __repr__(self):
return str(self)
class DTDForbidden(DefusedXmlException):
"""Document type definition is forbidden"""
def __init__(self, name, sysid, pubid):
super().__init__()
self.name = name
self.sysid = sysid
self.pubid = pubid
def __str__(self):
tpl = "DTDForbidden(name='{}', system_id={!r}, public_id={!r})"
return tpl.format(self.name, self.sysid, self.pubid)
class EntitiesForbidden(DefusedXmlException):
"""Entity definition is forbidden"""
def __init__(self, name, value, base, sysid, pubid, notation_name):
super().__init__()
self.name = name
self.value = value
self.base = base
self.sysid = sysid
self.pubid = pubid
self.notation_name = notation_name
def __str__(self):
tpl = "EntitiesForbidden(name='{}', system_id={!r}, public_id={!r})"
return tpl.format(self.name, self.sysid, self.pubid)
class ExternalReferenceForbidden(DefusedXmlException):
"""Resolving an external reference is forbidden"""
def __init__(self, context, base, sysid, pubid):
super().__init__()
self.context = context
self.base = base
self.sysid = sysid
self.pubid = pubid
def __str__(self):
tpl = "ExternalReferenceForbidden(system_id='{}', public_id={})"
return tpl.format(self.sysid, self.pubid)
class NotSupportedError(DefusedXmlException):
"""The operation is not supported"""
def _apply_defusing(defused_mod):
assert defused_mod is sys.modules[defused_mod.__name__]
stdlib_name = defused_mod.__origin__
__import__(stdlib_name, {}, {}, ["*"])
stdlib_mod = sys.modules[stdlib_name]
stdlib_names = set(dir(stdlib_mod))
for name, obj in vars(defused_mod).items():
if name.startswith("_") or name not in stdlib_names:
continue
setattr(stdlib_mod, name, obj)
return stdlib_mod

View file

@ -0,0 +1,107 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <christian@python.org>
# Licensed to PSF under a Contributor Agreement.
# See https://www.python.org/psf/license for licensing details.
"""Defused xml.dom.expatbuilder
"""
from __future__ import print_function, absolute_import
from xml.dom.expatbuilder import ExpatBuilder as _ExpatBuilder
from xml.dom.expatbuilder import Namespaces as _Namespaces
from .common import DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden
__origin__ = "xml.dom.expatbuilder"
class DefusedExpatBuilder(_ExpatBuilder):
"""Defused document builder"""
def __init__(
self, options=None, forbid_dtd=False, forbid_entities=True, forbid_external=True
):
_ExpatBuilder.__init__(self, options)
self.forbid_dtd = forbid_dtd
self.forbid_entities = forbid_entities
self.forbid_external = forbid_external
def defused_start_doctype_decl(self, name, sysid, pubid, has_internal_subset):
raise DTDForbidden(name, sysid, pubid)
def defused_entity_decl(
self, name, is_parameter_entity, value, base, sysid, pubid, notation_name
):
raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name)
def defused_unparsed_entity_decl(self, name, base, sysid, pubid, notation_name):
# expat 1.2
raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) # pragma: no cover
def defused_external_entity_ref_handler(self, context, base, sysid, pubid):
raise ExternalReferenceForbidden(context, base, sysid, pubid)
def install(self, parser):
_ExpatBuilder.install(self, parser)
if self.forbid_dtd:
parser.StartDoctypeDeclHandler = self.defused_start_doctype_decl
if self.forbid_entities:
# if self._options.entities:
parser.EntityDeclHandler = self.defused_entity_decl
parser.UnparsedEntityDeclHandler = self.defused_unparsed_entity_decl
if self.forbid_external:
parser.ExternalEntityRefHandler = self.defused_external_entity_ref_handler
class DefusedExpatBuilderNS(_Namespaces, DefusedExpatBuilder):
"""Defused document builder that supports namespaces."""
def install(self, parser):
DefusedExpatBuilder.install(self, parser)
if self._options.namespace_declarations:
parser.StartNamespaceDeclHandler = self.start_namespace_decl_handler
def reset(self):
DefusedExpatBuilder.reset(self)
self._initNamespaces()
def parse(file, namespaces=True, forbid_dtd=False, forbid_entities=True, forbid_external=True):
"""Parse a document, returning the resulting Document node.
'file' may be either a file name or an open file object.
"""
if namespaces:
build_builder = DefusedExpatBuilderNS
else:
build_builder = DefusedExpatBuilder
builder = build_builder(
forbid_dtd=forbid_dtd, forbid_entities=forbid_entities, forbid_external=forbid_external
)
if isinstance(file, str):
fp = open(file, "rb")
try:
result = builder.parseFile(fp)
finally:
fp.close()
else:
result = builder.parseFile(file)
return result
def parseString(
string, namespaces=True, forbid_dtd=False, forbid_entities=True, forbid_external=True
):
"""Parse a document from a string, returning the resulting
Document node.
"""
if namespaces:
build_builder = DefusedExpatBuilderNS
else:
build_builder = DefusedExpatBuilder
builder = build_builder(
forbid_dtd=forbid_dtd, forbid_entities=forbid_entities, forbid_external=forbid_external
)
return builder.parseString(string)

View file

@ -0,0 +1,61 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <christian@python.org>
# Licensed to PSF under a Contributor Agreement.
# See https://www.python.org/psf/license for licensing details.
"""Defused xml.sax.expatreader
"""
from __future__ import print_function, absolute_import
from xml.sax.expatreader import ExpatParser as _ExpatParser
from .common import DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden
__origin__ = "xml.sax.expatreader"
class DefusedExpatParser(_ExpatParser):
"""Defused SAX driver for the pyexpat C module."""
def __init__(
self,
namespaceHandling=0,
bufsize=2 ** 16 - 20,
forbid_dtd=False,
forbid_entities=True,
forbid_external=True,
):
super().__init__(namespaceHandling, bufsize)
self.forbid_dtd = forbid_dtd
self.forbid_entities = forbid_entities
self.forbid_external = forbid_external
def defused_start_doctype_decl(self, name, sysid, pubid, has_internal_subset):
raise DTDForbidden(name, sysid, pubid)
def defused_entity_decl(
self, name, is_parameter_entity, value, base, sysid, pubid, notation_name
):
raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name)
def defused_unparsed_entity_decl(self, name, base, sysid, pubid, notation_name):
# expat 1.2
raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) # pragma: no cover
def defused_external_entity_ref_handler(self, context, base, sysid, pubid):
raise ExternalReferenceForbidden(context, base, sysid, pubid)
def reset(self):
super().reset()
parser = self._parser
if self.forbid_dtd:
parser.StartDoctypeDeclHandler = self.defused_start_doctype_decl
if self.forbid_entities:
parser.EntityDeclHandler = self.defused_entity_decl
parser.UnparsedEntityDeclHandler = self.defused_unparsed_entity_decl
if self.forbid_external:
parser.ExternalEntityRefHandler = self.defused_external_entity_ref_handler
def create_parser(*args, **kwargs):
return DefusedExpatParser(*args, **kwargs)

View file

@ -0,0 +1,153 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <christian@python.org>
# Licensed to PSF under a Contributor Agreement.
# See https://www.python.org/psf/license for licensing details.
"""DEPRECATED Example code for lxml.etree protection
The code has NO protection against decompression bombs.
"""
from __future__ import print_function, absolute_import
import threading
import warnings
from lxml import etree as _etree
from .common import DTDForbidden, EntitiesForbidden, NotSupportedError
LXML3 = _etree.LXML_VERSION[0] >= 3
__origin__ = "lxml.etree"
tostring = _etree.tostring
warnings.warn(
"defusedxml.lxml is no longer supported and will be removed in a future release.",
category=DeprecationWarning,
stacklevel=2,
)
class RestrictedElement(_etree.ElementBase):
"""A restricted Element class that filters out instances of some classes"""
__slots__ = ()
# blacklist = (etree._Entity, etree._ProcessingInstruction, etree._Comment)
blacklist = _etree._Entity
def _filter(self, iterator):
blacklist = self.blacklist
for child in iterator:
if isinstance(child, blacklist):
continue
yield child
def __iter__(self):
iterator = super(RestrictedElement, self).__iter__()
return self._filter(iterator)
def iterchildren(self, tag=None, reversed=False):
iterator = super(RestrictedElement, self).iterchildren(tag=tag, reversed=reversed)
return self._filter(iterator)
def iter(self, tag=None, *tags):
iterator = super(RestrictedElement, self).iter(tag=tag, *tags)
return self._filter(iterator)
def iterdescendants(self, tag=None, *tags):
iterator = super(RestrictedElement, self).iterdescendants(tag=tag, *tags)
return self._filter(iterator)
def itersiblings(self, tag=None, preceding=False):
iterator = super(RestrictedElement, self).itersiblings(tag=tag, preceding=preceding)
return self._filter(iterator)
def getchildren(self):
iterator = super(RestrictedElement, self).__iter__()
return list(self._filter(iterator))
def getiterator(self, tag=None):
iterator = super(RestrictedElement, self).getiterator(tag)
return self._filter(iterator)
class GlobalParserTLS(threading.local):
"""Thread local context for custom parser instances"""
parser_config = {
"resolve_entities": False,
# 'remove_comments': True,
# 'remove_pis': True,
}
element_class = RestrictedElement
def createDefaultParser(self):
parser = _etree.XMLParser(**self.parser_config)
element_class = self.element_class
if self.element_class is not None:
lookup = _etree.ElementDefaultClassLookup(element=element_class)
parser.set_element_class_lookup(lookup)
return parser
def setDefaultParser(self, parser):
self._default_parser = parser
def getDefaultParser(self):
parser = getattr(self, "_default_parser", None)
if parser is None:
parser = self.createDefaultParser()
self.setDefaultParser(parser)
return parser
_parser_tls = GlobalParserTLS()
getDefaultParser = _parser_tls.getDefaultParser
def check_docinfo(elementtree, forbid_dtd=False, forbid_entities=True):
"""Check docinfo of an element tree for DTD and entity declarations
The check for entity declarations needs lxml 3 or newer. lxml 2.x does
not support dtd.iterentities().
"""
docinfo = elementtree.docinfo
if docinfo.doctype:
if forbid_dtd:
raise DTDForbidden(docinfo.doctype, docinfo.system_url, docinfo.public_id)
if forbid_entities and not LXML3:
# lxml < 3 has no iterentities()
raise NotSupportedError("Unable to check for entity declarations " "in lxml 2.x")
if forbid_entities:
for dtd in docinfo.internalDTD, docinfo.externalDTD:
if dtd is None:
continue
for entity in dtd.iterentities():
raise EntitiesForbidden(entity.name, entity.content, None, None, None, None)
def parse(source, parser=None, base_url=None, forbid_dtd=False, forbid_entities=True):
if parser is None:
parser = getDefaultParser()
elementtree = _etree.parse(source, parser, base_url=base_url)
check_docinfo(elementtree, forbid_dtd, forbid_entities)
return elementtree
def fromstring(text, parser=None, base_url=None, forbid_dtd=False, forbid_entities=True):
if parser is None:
parser = getDefaultParser()
rootelement = _etree.fromstring(text, parser, base_url=base_url)
elementtree = rootelement.getroottree()
check_docinfo(elementtree, forbid_dtd, forbid_entities)
return rootelement
XML = fromstring
def iterparse(*args, **kwargs):
raise NotSupportedError("defused lxml.etree.iterparse not available")

View file

@ -0,0 +1,63 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <christian@python.org>
# Licensed to PSF under a Contributor Agreement.
# See https://www.python.org/psf/license for licensing details.
"""Defused xml.dom.minidom
"""
from __future__ import print_function, absolute_import
from xml.dom.minidom import _do_pulldom_parse
from . import expatbuilder as _expatbuilder
from . import pulldom as _pulldom
__origin__ = "xml.dom.minidom"
def parse(
file, parser=None, bufsize=None, forbid_dtd=False, forbid_entities=True, forbid_external=True
):
"""Parse a file into a DOM by filename or file object."""
if parser is None and not bufsize:
return _expatbuilder.parse(
file,
forbid_dtd=forbid_dtd,
forbid_entities=forbid_entities,
forbid_external=forbid_external,
)
else:
return _do_pulldom_parse(
_pulldom.parse,
(file,),
{
"parser": parser,
"bufsize": bufsize,
"forbid_dtd": forbid_dtd,
"forbid_entities": forbid_entities,
"forbid_external": forbid_external,
},
)
def parseString(
string, parser=None, forbid_dtd=False, forbid_entities=True, forbid_external=True
):
"""Parse a file into a DOM from a string."""
if parser is None:
return _expatbuilder.parseString(
string,
forbid_dtd=forbid_dtd,
forbid_entities=forbid_entities,
forbid_external=forbid_external,
)
else:
return _do_pulldom_parse(
_pulldom.parseString,
(string,),
{
"parser": parser,
"forbid_dtd": forbid_dtd,
"forbid_entities": forbid_entities,
"forbid_external": forbid_external,
},
)

View file

@ -0,0 +1,41 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <christian@python.org>
# Licensed to PSF under a Contributor Agreement.
# See https://www.python.org/psf/license for licensing details.
"""Defused xml.dom.pulldom
"""
from __future__ import print_function, absolute_import
from xml.dom.pulldom import parse as _parse
from xml.dom.pulldom import parseString as _parseString
from .sax import make_parser
__origin__ = "xml.dom.pulldom"
def parse(
stream_or_string,
parser=None,
bufsize=None,
forbid_dtd=False,
forbid_entities=True,
forbid_external=True,
):
if parser is None:
parser = make_parser()
parser.forbid_dtd = forbid_dtd
parser.forbid_entities = forbid_entities
parser.forbid_external = forbid_external
return _parse(stream_or_string, parser, bufsize)
def parseString(
string, parser=None, forbid_dtd=False, forbid_entities=True, forbid_external=True
):
if parser is None:
parser = make_parser()
parser.forbid_dtd = forbid_dtd
parser.forbid_entities = forbid_entities
parser.forbid_external = forbid_external
return _parseString(string, parser)

View file

@ -0,0 +1,60 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <christian@python.org>
# Licensed to PSF under a Contributor Agreement.
# See https://www.python.org/psf/license for licensing details.
"""Defused xml.sax
"""
from __future__ import print_function, absolute_import
from xml.sax import InputSource as _InputSource
from xml.sax import ErrorHandler as _ErrorHandler
from . import expatreader
__origin__ = "xml.sax"
def parse(
source,
handler,
errorHandler=_ErrorHandler(),
forbid_dtd=False,
forbid_entities=True,
forbid_external=True,
):
parser = make_parser()
parser.setContentHandler(handler)
parser.setErrorHandler(errorHandler)
parser.forbid_dtd = forbid_dtd
parser.forbid_entities = forbid_entities
parser.forbid_external = forbid_external
parser.parse(source)
def parseString(
string,
handler,
errorHandler=_ErrorHandler(),
forbid_dtd=False,
forbid_entities=True,
forbid_external=True,
):
from io import BytesIO
if errorHandler is None:
errorHandler = _ErrorHandler()
parser = make_parser()
parser.setContentHandler(handler)
parser.setErrorHandler(errorHandler)
parser.forbid_dtd = forbid_dtd
parser.forbid_entities = forbid_entities
parser.forbid_external = forbid_external
inpsrc = _InputSource()
inpsrc.setByteStream(BytesIO(string))
parser.parse(inpsrc)
def make_parser(parser_list=[]):
return expatreader.create_parser()

View file

@ -0,0 +1,144 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <christian@python.org>
# Licensed to PSF under a Contributor Agreement.
# See https://www.python.org/psf/license for licensing details.
"""Defused xmlrpclib
Also defuses gzip bomb
"""
from __future__ import print_function, absolute_import
import io
from .common import DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden
__origin__ = "xmlrpc.client"
from xmlrpc.client import ExpatParser
from xmlrpc import client as xmlrpc_client
from xmlrpc import server as xmlrpc_server
from xmlrpc.client import gzip_decode as _orig_gzip_decode
from xmlrpc.client import GzipDecodedResponse as _OrigGzipDecodedResponse
try:
import gzip
except ImportError: # pragma: no cover
gzip = None
# Limit maximum request size to prevent resource exhaustion DoS
# Also used to limit maximum amount of gzip decoded data in order to prevent
# decompression bombs
# A value of -1 or smaller disables the limit
MAX_DATA = 30 * 1024 * 1024 # 30 MB
def defused_gzip_decode(data, limit=None):
"""gzip encoded data -> unencoded data
Decode data using the gzip content encoding as described in RFC 1952
"""
if not gzip: # pragma: no cover
raise NotImplementedError
if limit is None:
limit = MAX_DATA
f = io.BytesIO(data)
gzf = gzip.GzipFile(mode="rb", fileobj=f)
try:
if limit < 0: # no limit
decoded = gzf.read()
else:
decoded = gzf.read(limit + 1)
except IOError: # pragma: no cover
raise ValueError("invalid data")
f.close()
gzf.close()
if limit >= 0 and len(decoded) > limit:
raise ValueError("max gzipped payload length exceeded")
return decoded
class DefusedGzipDecodedResponse(gzip.GzipFile if gzip else object):
"""a file-like object to decode a response encoded with the gzip
method, as described in RFC 1952.
"""
def __init__(self, response, limit=None):
# response doesn't support tell() and read(), required by
# GzipFile
if not gzip: # pragma: no cover
raise NotImplementedError
self.limit = limit = limit if limit is not None else MAX_DATA
if limit < 0: # no limit
data = response.read()
self.readlength = None
else:
data = response.read(limit + 1)
self.readlength = 0
if limit >= 0 and len(data) > limit:
raise ValueError("max payload length exceeded")
self.stringio = io.BytesIO(data)
super().__init__(mode="rb", fileobj=self.stringio)
def read(self, n):
if self.limit >= 0:
left = self.limit - self.readlength
n = min(n, left + 1)
data = gzip.GzipFile.read(self, n)
self.readlength += len(data)
if self.readlength > self.limit:
raise ValueError("max payload length exceeded")
return data
else:
return super().read(n)
def close(self):
super().close()
self.stringio.close()
class DefusedExpatParser(ExpatParser):
def __init__(self, target, forbid_dtd=False, forbid_entities=True, forbid_external=True):
super().__init__(target)
self.forbid_dtd = forbid_dtd
self.forbid_entities = forbid_entities
self.forbid_external = forbid_external
parser = self._parser
if self.forbid_dtd:
parser.StartDoctypeDeclHandler = self.defused_start_doctype_decl
if self.forbid_entities:
parser.EntityDeclHandler = self.defused_entity_decl
parser.UnparsedEntityDeclHandler = self.defused_unparsed_entity_decl
if self.forbid_external:
parser.ExternalEntityRefHandler = self.defused_external_entity_ref_handler
def defused_start_doctype_decl(self, name, sysid, pubid, has_internal_subset):
raise DTDForbidden(name, sysid, pubid)
def defused_entity_decl(
self, name, is_parameter_entity, value, base, sysid, pubid, notation_name
):
raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name)
def defused_unparsed_entity_decl(self, name, base, sysid, pubid, notation_name):
# expat 1.2
raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) # pragma: no cover
def defused_external_entity_ref_handler(self, context, base, sysid, pubid):
raise ExternalReferenceForbidden(context, base, sysid, pubid)
def monkey_patch():
xmlrpc_client.FastParser = DefusedExpatParser
xmlrpc_client.GzipDecodedResponse = DefusedGzipDecodedResponse
xmlrpc_client.gzip_decode = defused_gzip_decode
if xmlrpc_server:
xmlrpc_server.gzip_decode = defused_gzip_decode
def unmonkey_patch():
xmlrpc_client.FastParser = None
xmlrpc_client.GzipDecodedResponse = _OrigGzipDecodedResponse
xmlrpc_client.gzip_decode = _orig_gzip_decode
if xmlrpc_server:
xmlrpc_server.gzip_decode = _orig_gzip_decode

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import requests
import requests.exceptions as exceptions
@ -18,7 +17,7 @@ LOG = getLogger('PLEX.download')
###############################################################################
class DownloadUtils():
class DownloadUtils(object):
"""
Manages any up/downloads with PKC. Careful to initiate correctly
Use startSession() to initiate.
@ -224,7 +223,11 @@ class DownloadUtils():
if r.status_code != 401:
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)
@ -258,12 +261,9 @@ 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 = utils.defused_etree.fromstring(r.content)
r = utils.etree.fromstring(r.content)
return r
except Exception:
r.encoding = 'utf-8'

View file

@ -4,9 +4,10 @@
Loads of different functions called in SEPARATE Python instances through
e.g. plugin://... calls. Hence be careful to only rely on window variables.
"""
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import sys
import copy
import xml.etree.ElementTree as etree
import xbmc
import xbmcplugin
@ -87,12 +88,10 @@ def directory_item(label, path, folder=True):
Adds a xbmcplugin.addDirectoryItem() directory itemlistitem
"""
listitem = ListItem(label, path=path)
listitem.setThumbnailImage(
"special://home/addons/plugin.video.plexkodiconnect/icon.png")
listitem.setArt(
{"fanart": "special://home/addons/plugin.video.plexkodiconnect/fanart.jpg"})
listitem.setArt(
{"landscape":"special://home/addons/plugin.video.plexkodiconnect/fanart.jpg"})
{'landscape':'special://home/addons/plugin.video.plexkodiconnect/fanart.jpg',
'fanart': 'special://home/addons/plugin.video.plexkodiconnect/fanart.jpg',
'thumb': 'special://home/addons/plugin.video.plexkodiconnect/icon.png'})
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
url=path,
listitem=listitem,
@ -248,12 +247,10 @@ def show_listing(xml, plex_type=None, section_id=None, synched=True, key=None):
widgets.KEY = key
# Process all items to show
all_items = mass_api(xml)
all_items = utils.process_method_on_list(widgets.generate_item, all_items)
all_items = utils.process_method_on_list(widgets.prepare_listitem,
all_items)
all_items = [widgets.generate_item(api) for api in all_items]
all_items = [widgets.prepare_listitem(item) for item in all_items]
# fill that listing...
all_items = utils.process_method_on_list(widgets.create_listitem,
all_items)
all_items = [widgets.create_listitem(item) for item in all_items]
xbmcplugin.addDirectoryItems(int(sys.argv[1]), all_items, len(all_items))
# end directory listing
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_UNSORTED)
@ -287,7 +284,7 @@ def get_video_files(plex_id, params):
app.init(entrypoint=True)
item = PF.GetPlexMetadata(plex_id)
try:
path = utils.try_decode(item[0][0][0].attrib['file'])
path = item[0][0][0].attrib['file']
except (TypeError, IndexError, AttributeError, KeyError):
LOG.error('Could not get file path for item %s', plex_id)
return xbmcplugin.endOfDirectory(int(sys.argv[1]))
@ -303,15 +300,14 @@ def get_video_files(plex_id, params):
if path_ops.exists(path):
for root, dirs, files in path_ops.walk(path):
for directory in dirs:
item_path = utils.try_encode(path_ops.path.join(root,
directory))
item_path = path_ops.path.join(root, directory)
listitem = ListItem(item_path, path=item_path)
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
url=item_path,
listitem=listitem,
isFolder=True)
for file in files:
item_path = utils.try_encode(path_ops.path.join(root, file))
item_path = path_ops.path.join(root, file)
listitem = ListItem(item_path, path=item_path)
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
url=file,
@ -356,23 +352,20 @@ def extra_fanart(plex_id, plex_path):
backdrops = api.artwork()['Backdrop']
for count, backdrop in enumerate(backdrops):
# Same ordering as in artwork
art_file = utils.try_encode(path_ops.path.join(
fanart_dir, "fanart%.3d.jpg" % count))
art_file = path_ops.path.join(fanart_dir, "fanart%.3d.jpg" % count)
listitem = ListItem("%.3d" % count, path=art_file)
xbmcplugin.addDirectoryItem(
handle=int(sys.argv[1]),
url=art_file,
listitem=listitem)
path_ops.copyfile(backdrop, utils.try_decode(art_file))
path_ops.copyfile(backdrop, art_file)
else:
LOG.info("Found cached backdrop.")
# Use existing cached images
fanart_dir = utils.try_decode(fanart_dir)
fanart_dir = fanart_dir
for root, _, files in path_ops.walk(fanart_dir):
root = utils.decode_path(root)
for file in files:
file = utils.decode_path(file)
art_file = utils.try_encode(path_ops.path.join(root, file))
art_file = path_ops.path.join(root, file)
listitem = ListItem(file, path=art_file)
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
url=art_file,
@ -423,6 +416,7 @@ def hub(content_type):
# We need to make sure that only entries that WORK are displayed
# WARNING: using xml.remove(child) in for-loop requires traversing from
# the end!
pkc_cont_watching = None
for entry in reversed(xml):
api = API(entry)
append = False
@ -439,6 +433,21 @@ def hub(content_type):
append = True
if not append:
xml.remove(entry)
# HACK ##################
# Merge Plex's "Continue watching" with "On deck"
if entry.get('key') == '/hubs/home/continueWatching':
pkc_cont_watching = copy.deepcopy(entry)
pkc_cont_watching.set('key', '/hubs/continueWatching')
title = pkc_cont_watching.get('title') or 'Continue Watching'
pkc_cont_watching.set('title', 'PKC %s' % title)
if pkc_cont_watching:
for i, entry in enumerate(xml):
if entry.get('key') == '/hubs/home/continueWatching':
xml.insert(i + 1, pkc_cont_watching)
break
# END HACK ##################
show_listing(xml)
@ -488,7 +497,7 @@ def browse_plex(key=None, plex_type=None, section_id=None, synched=True,
if prompt is None:
# User cancelled
return
prompt = prompt.strip().decode('utf-8')
prompt = prompt.strip()
args['query'] = prompt
xml = DU().downloadUrl(utils.extend_url('{server}%s' % key, args))
try:
@ -499,7 +508,7 @@ def browse_plex(key=None, plex_type=None, section_id=None, synched=True,
return
if xml[0].tag == 'Hub':
# E.g. when hitting the endpoint '/hubs/search'
answ = utils.etree.Element(xml.tag, attrib=xml.attrib)
answ = etree.Element(xml.tag, attrib=xml.attrib)
for hub in xml:
if not utils.cast(int, hub.get('size')):
# Empty category

View file

@ -0,0 +1,31 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
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,12 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from xbmc import executebuiltin
from . import utils
from .utils import etree
import xml.etree.ElementTree as etree
from . import path_ops
from . import migration
from .downloadutils import DownloadUtils as DU, exceptions
@ -212,8 +211,7 @@ class InitialSetup(object):
not set before
"""
answer = True
chk = PF.check_connection(app.CONN.server,
verifySSL=True if v.KODIVERSION >= 18 else False)
chk = PF.check_connection(app.CONN.server, verifySSL=True)
if chk is False:
LOG.warn('Could not reach PMS %s', app.CONN.server)
answer = False
@ -241,18 +239,13 @@ class InitialSetup(object):
"""
Checks for server's connectivity. Returns check_connection result
"""
if server['local']:
# Deactive SSL verification if the server is local for Kodi 17
verifySSL = True if v.KODIVERSION >= 18 else False
else:
verifySSL = True
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)
verifySSL=True)
def pick_pms(self, showDialog=False, inform_of_search=False):
"""
@ -287,7 +280,6 @@ class InitialSetup(object):
}
or None if unsuccessful
"""
server = None
# If no server is set, let user choose one
if not app.CONN.server or not app.CONN.machine_identifier:
showDialog = True
@ -411,7 +403,7 @@ class InitialSetup(object):
utils.messageDialog(
utils.lang(29999),
'%s %s\n%s' % (utils.lang(39013),
server['name'].decode('utf-8'),
server['name'],
utils.lang(39014)))
if self.plex_tv_sign_in() is False:
# Exit while loop if user cancels
@ -435,7 +427,6 @@ class InitialSetup(object):
utils.settings('plex_servername', server['name'])
utils.settings('plex_serverowned',
'true' if server['owned'] else 'false')
utils.settings('accessToken', server['token'])
# Careful to distinguish local from remote PMS
if server['local']:
scheme = server['scheme']
@ -551,9 +542,7 @@ class InitialSetup(object):
# Display a warning if Kodi puts ALL movies into the queue, basically
# breaking playback reporting for PKC
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},
@ -561,12 +550,6 @@ class InitialSetup(object):
# {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':
@ -576,7 +559,6 @@ class InitialSetup(object):
# Warning: Kodi setting "Play next video automatically" is
# enabled. This could break PKC. Deactivate?
if utils.yesno_dialog(utils.lang(29999), utils.lang(30003)):
if v.KODIVERSION >= 18:
for i in (1, 2, 3):
try:
settings.remove(i)
@ -584,9 +566,6 @@ class InitialSetup(object):
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)
@ -634,7 +613,11 @@ class InitialSetup(object):
app.ACCOUNT.load()
app.SYNC.load()
return
LOG.info('Showing install questions')
if not utils.default_kodi_skin_warning_message():
LOG.info('Aborting initial setup due to skin')
return
# Additional settings where the user needs to choose
# Direct paths (\\NAS\mymovie.mkv) or addon (http)?
goto_settings = False
@ -689,10 +672,10 @@ class InitialSetup(object):
# 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=utils.lang(29999), line1=utils.lang(39076))
# dialog.ok(heading=utils.lang(29999), message=utils.lang(39076))
# Need to tell about our image source for collections: themoviedb.org
# dialog.ok(heading=utils.lang(29999), line1=utils.lang(39717))
# dialog.ok(heading=utils.lang(29999), message=utils.lang(39717))
# Make sure that we only ask these questions upon first installation
utils.settings('InstallQuestionsAnswered', value='true')

View file

@ -1,6 +1,5 @@
#!/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

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from ntpath import dirname
@ -162,7 +161,7 @@ class ItemBase(object):
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():
for provider, provider_id in api.guids.items():
kodi_unique_ids[provider] = self.kodidb.add_uniqueid(
kodi_id,
api.kodi_type,

View file

@ -1,14 +1,26 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import re
import os
import string
from .common import ItemBase
from ..plex_api import API
from .. import app, variables as v, plex_functions as PF
from ..path_ops import append_os_sep
LOG = getLogger('PLEX.movies')
# Tolerance in years if comparing videos as equal
VIDEOYEAR_TOLERANCE = 1
PUNCTUATION_TRANSLATION = {ord(char): None for char in string.punctuation}
# Punctuation removed in original strings!!
# Matches '2010 The Year We Make Contact 1984'
# from '2010 The Year We Make Contact 1984 720p webrip'
REGEX_MOVIENAME_AND_YEAR = re.compile(
r'''(.+)((?:19|20)\d{2}).*(?!((19|20)\d{2}))''')
class Movie(ItemBase):
"""
@ -38,11 +50,39 @@ class Movie(ItemBase):
fullpath, path, filename = api.fullpath()
if app.SYNC.direct_paths and not fullpath.startswith('http'):
kodi_pathid = self.kodidb.add_path(path,
if api.subtype:
# E.g. homevideos, which have "subtype" flag set
# Homevideo directories need to be flat by Plex' instructions
library_path, video_path = path, path
else:
# Normal movie libraries
library_path, video_path, filename = split_movie_path(fullpath)
if library_path == video_path:
# "Flat" folder structure where e.g. movies lie all in 1 dir
# E.g.
# 'C:\\Movies\\Pulp Fiction (1994).mkv'
kodi_pathid = self.kodidb.add_path(library_path,
content='movies',
scraper='metadata.local')
path = library_path
kodi_parent_pathid = kodi_pathid
else:
# Plex library contains folders named identical to the
# video file, e.g.
# 'C:\\Movies\\Pulp Fiction (1994)\\Pulp Fiction (1994).mkv'
# Add the "parent" path for the Plex library
kodi_parent_pathid = self.kodidb.add_path(
library_path,
content='movies',
scraper='metadata.local')
# Add this movie's path
kodi_pathid = self.kodidb.add_path(
video_path,
id_parent_path=kodi_parent_pathid)
path = video_path
else:
kodi_pathid = self.kodidb.get_path(path)
kodi_parent_pathid = kodi_pathid
if update_item:
LOG.info('UPDATE movie plex_id: %s - %s', plex_id, api.title())
@ -106,8 +146,8 @@ class Movie(ItemBase):
api.list_to_string(api.studios()),
api.trailer(),
api.list_to_string(api.countries()),
fullpath,
kodi_pathid,
path,
kodi_parent_pathid,
api.premiere_date(),
api.userrating())
@ -133,6 +173,7 @@ class Movie(ItemBase):
kodi_id=kodi_id,
kodi_fileid=file_id,
kodi_pathid=kodi_pathid,
trailer_synced=bool(api.trailer()),
last_sync=self.last_sync)
def remove(self, plex_id, plex_type=None):
@ -195,9 +236,10 @@ class Movie(ItemBase):
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
tags.append(set_name)
# 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)
@ -242,3 +284,73 @@ class Movie(ItemBase):
return unique_ids.get('imdb',
unique_ids.get('tmdb',
unique_ids.get('tvdb')))
def split_movie_path(path):
"""
Implements Plex' video naming convention for movies:
https://support.plex.tv/articles/naming-and-organizing-your-movie-media-files/
Splits a video's path into its librarypath, potential video folder, and
filename.
E.g. path = 'C:\\Movies\\Pulp Fiction (1994)\\Pulp Fiction (1994).mkv'
returns the tuple
('C:\\Movies\\',
'C:\\Movies\\Pulp Fiction (1994)\\',
'Pulp Fiction (1994).mkv')
E.g. path = 'C:\\Movies\\Pulp Fiction (1994).mkv'
returns the tuple
('C:\\Movies\\',
'C:\\Movies\\',
'Pulp Fiction (1994).mkv')
"""
basename, filename = os.path.split(path)
library_path, videofolder = os.path.split(basename)
clean_filename = _clean_name(os.path.splitext(filename)[0])
clean_videofolder = _clean_name(videofolder)
try:
parsed_filename = _parse_videoname_and_year(clean_filename)
except (TypeError, IndexError):
LOG.warn('Could not parse video path, be sure to follow the Plex '
'naming guidelines!! We failed to parse this path: %s', path)
# Be on the safe side and assume that the movie folder structure is
# flat
return append_os_sep(basename), append_os_sep(basename), filename
try:
parsed_videofolder = _parse_videoname_and_year(clean_videofolder)
except (TypeError, IndexError):
# e.g. no year to parse => flat structure
return append_os_sep(basename), append_os_sep(basename), filename
if _parsed_names_alike(parsed_filename, parsed_videofolder):
# e.g.
# filename = The Master.(2012).720p.Blu-ray.axed.mkv
# videofolder = The Master 2012
# or
# filename = National Lampoon's Christmas Vacation (1989)
# [x264-Bluray-1080p DTS-2.0]
# videofolder = Christmas Vacation 1989
return append_os_sep(library_path), append_os_sep(basename), filename
else:
# Flat movie file-stuctrue, all movies in one big directory
return append_os_sep(basename), append_os_sep(basename), filename
def _parsed_names_alike(name1, name2):
return (abs(name2[1] - name1[1]) <= VIDEOYEAR_TOLERANCE and
(name1[0] in name2[0] or name2[0] in name1[0]))
def _clean_name(name):
"""
Returns name with all whitespaces (regex "\\s") and punctuation
(string.punctuation) characters removed; all characters in lowercase
"""
return re.sub('\\s', '', name).translate(PUNCTUATION_TRANSLATION).lower()
def _parse_videoname_and_year(name):
parsed = REGEX_MOVIENAME_AND_YEAR.search(name)
return parsed[1], int(parsed[2])

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .common import ItemBase
@ -127,17 +126,12 @@ class MusicMixin(object):
# 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
'''
self.kodidb.delete_album_from_discography(kodi_id)
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)
@ -177,16 +171,6 @@ class Artist(MusicMixin, ItemBase):
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:
@ -201,8 +185,6 @@ class Artist(MusicMixin, ItemBase):
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:
@ -282,82 +264,47 @@ class Album(MusicMixin, ItemBase):
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(),
api.premiere_date(),
# TODO: as soon as Plex supports the original
# release date (Kodi: strOrigReleaseDate)
api.premiere_date(),
compilation,
api.plot(),
thumb,
api.list_to_string(api.studios()),
api.kodi_type,
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',
api.kodi_type,
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(),
api.premiere_date(),
# TODO: as soon as Plex supports the original
# release date (Kodi: strOrigReleaseDate)
api.premiere_date(),
compilation,
api.plot(),
thumb,
api.list_to_string(api.studios()),
api.kodi_type,
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')
api.kodi_type)
self.kodidb.add_albumartist(artist_id, kodi_id, api.artist_name())
if v.KODIVERSION < 18:
self.kodidb.add_discography(artist_id, name, api.year())
self.kodidb.add_music_genres(kodi_id,
api.genres(),
v.KODI_TYPE_ALBUM)
if app.SYNC.artwork:
self.kodidb.modify_artwork(artworks,
kodi_id,
@ -438,30 +385,19 @@ class Song(MusicMixin, ItemBase):
# 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(),
api.premiere_date(),
# TODO: as soon as Plex supports the original
# release date (Kodi: strOrigReleaseDate)
api.premiere_date(),
None,
None,
None,
'single',
None,
None,
timing.unix_date_to_kodi(self.last_sync),
@ -521,43 +457,31 @@ class Song(MusicMixin, ItemBase):
moods.append(entry.attrib['tag'])
mood = api.list_to_string(moods)
_, path, filename = api.fullpath()
audio_codec = api.audio_codec()
# 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,
api.premiere_date(),
# TODO: as soon as Plex supports the original
# release date (Kodi: strOrigReleaseDate)
api.premiere_date(),
filename,
api.viewcount(),
api.lastplayed(),
api.userrating(),
comment,
mood,
audio_codec['bitrate'] or 0,
audio_codec['samplingrate'] or 0,
audio_codec['channels'] or 0,
api.date_created(),
kodi_id)
# OR ADD THE SONG #####
@ -565,9 +489,6 @@ class Song(MusicMixin, ItemBase):
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,
@ -576,7 +497,10 @@ class Song(MusicMixin, ItemBase):
title,
track,
api.runtime(),
year,
api.premiere_date(),
# TODO: as soon as Plex supports the original
# release date (Kodi: strOrigReleaseDate)
api.premiere_date(),
filename,
musicBrainzId,
api.viewcount(),
@ -585,33 +509,10 @@ class Song(MusicMixin, ItemBase):
0,
0,
mood,
audio_codec['bitrate'] or 0,
audio_codec['samplingrate'] or 0,
audio_codec['channels'] or 0,
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

View file

@ -1,6 +1,5 @@
#!/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
@ -270,6 +269,7 @@ class Show(TvShowMixin, ItemBase):
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):
@ -279,7 +279,7 @@ class Season(TvShowMixin, ItemBase):
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(),
'Kodi', api.plex_type, api.plex_id, api.season_name(),
section_id or api.library_section_id())
return
plex_id = api.plex_id
@ -317,15 +317,24 @@ class Season(TvShowMixin, ItemBase):
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.title())
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.title())
kodi_id = self.kodidb.add_season(parent_id, api.index())
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,

View file

@ -4,7 +4,6 @@
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 xbmc import executeJSONRPC
@ -85,7 +84,7 @@ def get_player_ids():
Returns a list of all the active Kodi player ids (usually 3) as int
"""
ret = []
for player in get_players().values():
for player in list(get_players().values()):
ret.append(player['playerid'])
return ret
@ -170,12 +169,12 @@ def stop():
def seek_to(offset):
"""
Seeks all Kodi players to offset [int] in milliseconds
Seeks all Kodi players to offset [int] in seconds
"""
for playerid in get_player_ids():
return JsonRPC("Player.Seek").execute(
{"playerid": playerid,
"value": timing.millis_to_kodi_time(offset)})
"value": {'time': timing.millis_to_kodi_time(int(offset * 1000))}})
def smallforward():
@ -421,6 +420,50 @@ 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_video_stream_index(playerid):
"""
Returns the currently active video stream index [int]
"""
return JsonRPC('Player.GetProperties').execute({
'playerid': playerid,
'properties': ['currentvideostream']})['result']['currentvideostream']['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:
@ -586,6 +629,15 @@ def item_details(kodi_id, kodi_type):
ret = JsonRPC(json).execute({'%sid' % kodi_type: kodi_id,
'properties': fields})
try:
return ret['result']['%sdetails' % kodi_type]
ret = ret['result']['%sdetails' % kodi_type]
except (KeyError, TypeError):
return {}
if kodi_type == v.KODI_TYPE_SHOW:
# append watched counts to tvshow details
ret["extraproperties"] = {
"totalseasons": str(ret["season"]),
"totalepisodes": str(ret["episode"]),
"watchedepisodes": str(ret["watchedepisodes"]),
"unwatchedepisodes": str(ret["episode"] - ret["watchedepisodes"])
}
return ret

View file

@ -5,88 +5,45 @@ 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_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"]
FIELDS_MOVIES.append("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']
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'
}
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

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .common import KODIDB_LOCK
@ -21,7 +20,6 @@ def kodiid_from_filename(path, kodi_type=None, db_type=None):
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:
@ -74,7 +72,7 @@ def reset_cached_images():
for path in paths:
new_path = path_ops.translate_path('special://thumbnails/%s' % path)
try:
path_ops.makedirs(path_ops.encode_path(new_path))
path_ops.makedirs(new_path)
except OSError as err:
LOG.warn('Could not create thumbnail directory %s: %s',
new_path, err)

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from threading import Lock
from .. import db, path_ops
@ -65,7 +64,7 @@ class KodiDBBase(object):
"""
Pass in an artworks dict (see PlexAPI) to set an items artwork.
"""
for kodi_art, url in artworks.iteritems():
for kodi_art, url in artworks.items():
self.add_art(url, kodi_id, kodi_type, kodi_art)
@db.catch_operationalerrors
@ -84,7 +83,7 @@ class KodiDBBase(object):
"""
Pass in an artworks dict (see PlexAPI) to set an items artwork.
"""
for kodi_art, url in artworks.iteritems():
for kodi_art, url in artworks.items():
self.modify_art(url, kodi_id, kodi_type, kodi_art)
@db.catch_operationalerrors

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from . import common
@ -49,7 +48,6 @@ class KodiMusicDB(common.KodiDBBase):
strRole)
VALUES (?, ?)
''', (1, 'Artist'))
if v.KODIVERSION >= 18:
self.cursor.execute('DELETE FROM versiontagscan')
self.cursor.execute('''
INSERT INTO versiontagscan(
@ -106,26 +104,6 @@ class KodiMusicDB(common.KodiDBBase):
self.cursor.execute('DELETE FROM song_artist WHERE idSong = ?',
(song_id, ))
@db.catch_operationalerrors
def delete_album_from_discography(self, album_id):
"""
Removes the album with id album_id from the table discography
"""
# Need to get the album name as a string first!
self.cursor.execute('SELECT strAlbum, iYear FROM album WHERE idAlbum = ? LIMIT 1',
(album_id, ))
try:
name, year = self.cursor.fetchone()
except TypeError:
return
self.cursor.execute('SELECT idArtist FROM album_artist WHERE idAlbum = ? LIMIT 1',
(album_id, ))
artist = self.cursor.fetchone()
if not artist:
return
self.cursor.execute('DELETE FROM discography WHERE idArtist = ? AND strAlbum = ? AND strYear = ?',
(artist[0], name, year))
@db.catch_operationalerrors
def delete_song_from_song_genre(self, song_id):
"""
@ -180,87 +158,6 @@ class KodiMusicDB(common.KodiDBBase):
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):
"""
@ -274,15 +171,16 @@ class KodiMusicDB(common.KodiDBBase):
strMusicBrainzAlbumID,
strArtistDisp,
strGenres,
iYear,
strReleaseDate,
strOrigReleaseDate,
bCompilation,
strReview,
strImage,
strLabel,
strType,
iUserrating,
lastScraped,
strReleaseType)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
else:
args = list(args)
@ -294,14 +192,16 @@ class KodiMusicDB(common.KodiDBBase):
strMusicBrainzAlbumID,
strArtistDisp,
strGenres,
iYear,
strReleaseDate,
strOrigReleaseDate,
bCompilation,
strReview,
strLabel,
strType,
iUserrating,
lastScraped,
strReleaseType)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
@db.catch_operationalerrors
@ -313,11 +213,12 @@ class KodiMusicDB(common.KodiDBBase):
strMusicBrainzAlbumID = ?,
strArtistDisp = ?,
strGenres = ?,
iYear = ?,
strReleaseDate = ?,
strOrigReleaseDate = ?,
bCompilation = ?,
strReview = ?,
strImage = ?,
strLabel = ?,
strType = ?,
iUserrating = ?,
lastScraped = ?,
strReleaseType = ?
@ -332,7 +233,8 @@ class KodiMusicDB(common.KodiDBBase):
strMusicBrainzAlbumID = ?,
strArtistDisp = ?,
strGenres = ?,
iYear = ?,
strReleaseDate = ?,
strOrigReleaseDate = ?,
bCompilation = ?,
strReview = ?,
strLabel = ?,
@ -352,16 +254,6 @@ class KodiMusicDB(common.KodiDBBase):
VALUES (?, ?, ?)
''', (artist_id, kodi_id, artistname))
@db.catch_operationalerrors
def add_discography(self, artist_id, albumname, year):
self.cursor.execute('''
INSERT OR REPLACE INTO discography(
idArtist,
strAlbum,
strYear)
VALUES (?, ?, ?)
''', (artist_id, albumname, year))
@db.catch_operationalerrors
def add_music_genres(self, kodiid, genres, mediatype):
"""
@ -425,7 +317,8 @@ class KodiMusicDB(common.KodiDBBase):
strTitle,
iTrack,
iDuration,
iYear,
strReleaseDate,
strOrigReleaseDate,
strFileName,
strMusicBrainzTrackID,
iTimesPlayed,
@ -434,33 +327,11 @@ class KodiMusicDB(common.KodiDBBase):
iStartOffset,
iEndOffset,
mood,
iBitRate,
iSampleRate,
iChannels,
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
@db.catch_operationalerrors
@ -473,13 +344,17 @@ class KodiMusicDB(common.KodiDBBase):
strTitle = ?,
iTrack = ?,
iDuration = ?,
iYear = ?,
strReleaseDate = ?,
strOrigReleaseDate = ?,
strFilename = ?,
iTimesPlayed = ?,
lastplayed = ?,
rating = ?,
comment = ?,
mood = ?,
iBitRate = ?,
iSampleRate = ?,
iChannels = ?,
dateAdded = ?
WHERE idSong = ?
''', (args))
@ -493,27 +368,6 @@ class KodiMusicDB(common.KodiDBBase):
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, ))
@ -557,19 +411,6 @@ class KodiMusicDB(common.KodiDBBase):
@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 = ?,
@ -598,22 +439,6 @@ class KodiMusicDB(common.KodiDBBase):
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):
"""
@ -641,9 +466,6 @@ class KodiMusicDB(common.KodiDBBase):
@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, ))
@ -656,5 +478,3 @@ class KodiMusicDB(common.KodiDBBase):
(kodi_id, ))
self.cursor.execute('DELETE FROM song_artist WHERE idArtist = ?',
(kodi_id, ))
self.cursor.execute('DELETE FROM discography WHERE idArtist = ?',
(kodi_id, ))

View file

@ -1,7 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from . import common

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from sqlite3 import IntegrityError
@ -46,13 +45,15 @@ class KodiVideoDB(common.KodiDBBase):
strContent,
strScraper,
noUpdate,
exclude)
VALUES (?, ?, ?, ?, ?)
exclude,
allAudio)
VALUES (?, ?, ?, ?, ?, ?)
'''
self.cursor.execute(query, (path,
kind,
'metadata.local',
1,
0,
0))
@db.catch_operationalerrors
@ -61,9 +62,8 @@ class KodiVideoDB(common.KodiDBBase):
Video DB: Adds all subdirectories to path table while setting a "trail"
of parent path ids
"""
parentpath = path_ops.path.abspath(
path_ops.path.join(path,
path_ops.decode_path(path_ops.path.pardir)))
parentpath = path_ops.path.split(path_ops.path.split(path)[0])[0]
parentpath = path_ops.append_os_sep(parentpath)
pathid = self.get_path(parentpath)
if pathid is None:
self.cursor.execute('''
@ -110,11 +110,13 @@ class KodiVideoDB(common.KodiDBBase):
idParentPath,
strContent,
strScraper,
noUpdate)
VALUES (?, ?, ?, ?, ?, ?)
noUpdate,
exclude,
allAudio)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''',
(path, date_added, id_parent_path, content,
scraper, 1))
scraper, 1, 0, 0))
pathid = self.cursor.lastrowid
return pathid
@ -318,7 +320,7 @@ class KodiVideoDB(common.KodiDBBase):
for the elmement kodi_id, kodi_type.
Will also delete a freshly orphaned actor entry.
"""
for kind, people_list in people.iteritems():
for kind, people_list in people.items():
self._add_people_kind(kodi_id, kodi_type, kind, people_list)
@db.catch_operationalerrors
@ -363,7 +365,7 @@ class KodiVideoDB(common.KodiDBBase):
for kind, people_list in (people if people else
{'actor': [],
'director': [],
'writer': []}).iteritems():
'writer': []}).items():
self._modify_people_kind(kodi_id, kodi_type, kind, people_list)
@db.catch_operationalerrors
@ -479,6 +481,31 @@ class KodiVideoDB(common.KodiDBBase):
(kodi_id, kodi_type))
return dict(self.cursor.fetchall())
def get_trailer(self, kodi_id, kodi_type):
"""
Returns the trailer's URL for kodi_type from the Kodi database or None
"""
if kodi_type == v.KODI_TYPE_MOVIE:
self.cursor.execute('SELECT c19 FROM movie WHERE idMovie=?',
(kodi_id, ))
else:
raise NotImplementedError(f'trailers for {kodi_type} not implemented')
try:
return self.cursor.fetchone()[0]
except TypeError:
pass
@db.catch_operationalerrors
def set_trailer(self, kodi_id, kodi_type, url):
"""
Writes the trailer's url to the Kodi DB
"""
if kodi_type == v.KODI_TYPE_MOVIE:
self.cursor.execute('UPDATE movie SET c19=? WHERE idMovie=?',
(url, kodi_id))
else:
raise NotImplementedError(f'trailers for {kodi_type} not implemented')
@db.catch_operationalerrors
def modify_streams(self, fileid, streamdetails=None, runtime=None):
"""
@ -575,6 +602,24 @@ class KodiVideoDB(common.KodiDBBase):
return
return movie_id, typus
def file_id_from_id(self, kodi_id, kodi_type):
"""
Returns the Kodi file_id for the item with kodi_id and kodi_type or
None
"""
if kodi_type == v.KODI_TYPE_MOVIE:
identifier = 'idMovie'
elif kodi_type == v.KODI_TYPE_EPISODE:
identifier = 'idEpisode'
else:
return
self.cursor.execute('SELECT idFile FROM %s WHERE %s = ? LIMIT 1'
% (kodi_type, identifier), (kodi_id, ))
try:
return self.cursor.fetchone()[0]
except TypeError:
pass
def get_resume(self, file_id):
"""
Returns the first resume point in seconds (int) if found, else None for
@ -718,15 +763,32 @@ class KodiVideoDB(common.KodiDBBase):
self.cursor.execute('DELETE FROM sets WHERE idSet = ?', (set_id,))
@db.catch_operationalerrors
def add_season(self, showid, seasonnumber):
def add_season(self, showid, seasonnumber, name, userrating):
"""
Adds a TV show season to the Kodi video DB or simply returns the ID,
if there already is an entry in the DB
"""
self.cursor.execute('INSERT INTO seasons(idShow, season) VALUES (?, ?)',
(showid, seasonnumber))
self.cursor.execute('''
INSERT INTO seasons(
idShow, season, name, userrating)
VALUES (?, ?, ?, ?)
''', (showid, seasonnumber, name, userrating))
return self.cursor.lastrowid
@db.catch_operationalerrors
def update_season(self, seasonid, showid, seasonnumber, name, userrating):
"""
Updates a TV show season with a certain seasonid
"""
self.cursor.execute('''
UPDATE seasons
SET idShow = ?,
season = ?,
name = ?,
userrating = ?
WHERE idSeason = ?
''', (showid, seasonnumber, name, userrating, seasonid))
@db.catch_operationalerrors
def add_uniqueid(self, *args):
"""

View file

@ -3,7 +3,6 @@
"""
PKC Kodi Monitoring implementation
"""
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from json import loads
import copy
@ -14,11 +13,13 @@ import xbmc
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.kodimonitor')
@ -27,8 +28,10 @@ class KodiMonitor(xbmc.Monitor):
"""
PKC implementation of the Kodi Monitor class. Invoke only once.
"""
def __init__(self):
self._already_slept = False
self._switched_to_plex_streams = True
xbmc.Monitor.__init__(self)
for playerid in app.PLAYSTATE.player_states:
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
@ -58,12 +61,15 @@ class KodiMonitor(xbmc.Monitor):
Called when a bunch of different stuff happens on the Kodi side
"""
if data:
data = loads(data, 'utf-8')
data = loads(data)
LOG.debug("Method: %s Data: %s", method, data)
if method == "Player.OnPlay":
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":
with app.APP.lock_playqueues:
_playback_cleanup(ended=data.get('end'))
@ -82,6 +88,7 @@ class KodiMonitor(xbmc.Monitor):
with app.APP.lock_playqueues:
self._playlist_onclear(data)
elif method == "VideoLibrary.OnUpdate":
with app.APP.lock_playqueues:
_videolibrary_onupdate(data)
elif method == "VideoLibrary.OnRemove":
pass
@ -175,7 +182,7 @@ class KodiMonitor(xbmc.Monitor):
try:
for i, item in enumerate(items):
PL.add_item_to_plex_playqueue(playqueue, i + 1, kodi_item=item)
except PL.PlaylistError:
except exceptions.PlaylistError:
LOG.info('Could not build Plex playlist for: %s', items)
def _json_item(self, playerid):
@ -210,7 +217,7 @@ class KodiMonitor(xbmc.Monitor):
play_info = json.loads(play_info)
app.APP.player.stop()
handle = 'RunPlugin(%s)' % play_info.get('handle')
xbmc.executebuiltin(handle.encode('utf-8'))
xbmc.executebuiltin(handle)
def PlayBackStart(self, data):
"""
@ -289,7 +296,7 @@ class KodiMonitor(xbmc.Monitor):
LOG.debug('Detected different path')
try:
tmp_plex_id = int(utils.REGEX_PLEX_ID.findall(path)[0])
except IndexError:
except (IndexError, TypeError):
LOG.debug('No Plex id in path, need to init playqueue')
initialize = True
else:
@ -312,7 +319,7 @@ class KodiMonitor(xbmc.Monitor):
return
try:
item = PL.init_plex_playqueue(playqueue, plex_id=plex_id)
except PL.PlaylistError:
except exceptions.PlaylistError:
LOG.info('Could not initialize the Plex playlist')
return
item.file = path
@ -335,6 +342,9 @@ 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
@ -349,14 +359,50 @@ class KodiMonitor(xbmc.Monitor):
status['plex_type'] = plex_type
status['playmethod'] = item.playmethod
status['playcount'] = item.playcount
try:
status['external_player'] = app.APP.player.isExternalPlayer() == 1
except AttributeError:
# Kodi version < 17
pass
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
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
# Wait a bit because JSON responses won't be ready otherwise
if app.APP.monitor.waitForAbort(2):
# In case PKC needs to quit
return
item.init_kodi_streams()
item.switch_to_plex_stream('video')
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)
def _playback_cleanup(ended=False):
@ -367,6 +413,9 @@ def _playback_cleanup(ended=False):
"""
LOG.debug('playback_cleanup called. Active players: %s',
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)
app.CONN.plex_transient_token = None
@ -529,11 +578,10 @@ def _next_episode(current_api):
current_api.grandparent_title())
return
try:
next_api = API(xml[counter + 1])
return API(xml[counter + 1])
except IndexError:
# Was the last episode
return
return next_api
pass
def _complete_artwork_keys(info):
@ -559,7 +607,7 @@ def _notify_upnext(item):
"""
if not item.plex_type == v.PLEX_TYPE_EPISODE:
return
this_api = API(item.xml)
this_api = item.api
next_api = _next_episode(this_api)
if next_api is None:
return
@ -583,11 +631,11 @@ def _notify_upnext(item):
}
_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))
sender = v.ADDON_ID
method = 'upnext_data'
data = binascii.hexlify(json.dumps(info).encode('utf-8'))
data = '\\"[\\"{0}\\"]\\"'.format(data)
xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data))
xbmc.executebuiltin(f'NotifyAll({sender}, {method}, {data})')
def _videolibrary_onupdate(data):
@ -595,17 +643,34 @@ 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}
"""
playcount = data.get('playcount')
item = data.get('item')
if playcount is None or item is None:
return
item = data.get('item') if 'item' in data else data
try:
kodi_id = item['id']
kodi_type = item['type']
except (KeyError, TypeError):
LOG.info("Item is invalid for playstate update.")
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

View file

@ -1,9 +1,7 @@
# -*- 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 .additional_metadata import MetadataThread, ProcessMetadataTask
from .sections import force_full_sync, delete_files, clear_window_vars

View file

@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from . import additional_metadata_tmdb
from ..plex_db import PlexDB
from .. import backgroundthread, utils
from .. import variables as v, app
from ..exceptions import ProcessingNotDone
logger = getLogger('PLEX.sync.metadata')
BATCH_SIZE = 500
SUPPORTED_METADATA = {
v.PLEX_TYPE_MOVIE: (
('missing_trailers', additional_metadata_tmdb.process_trailers),
('missing_fanart', additional_metadata_tmdb.process_fanart),
),
v.PLEX_TYPE_SHOW: (
('missing_fanart', additional_metadata_tmdb.process_fanart),
),
}
def processing_is_activated(item_getter):
"""Checks the PKC settings whether processing is even activated."""
if item_getter == 'missing_fanart':
return utils.settings('FanartTV') == 'true'
return True
class MetadataThread(backgroundthread.KillableThread):
"""This will potentially take hours!"""
def __init__(self, callback, refresh=False):
self.callback = callback
self.refresh = refresh
super(MetadataThread, self).__init__()
def should_suspend(self):
return self._suspended or app.APP.is_playing_video
def _process_in_batches(self, item_getter, processor, plex_type):
offset = 0
while True:
with PlexDB() as plexdb:
# Keep DB connection open only for a short period of time!
if self.refresh:
# Simply grab every single item if we want to refresh
func = plexdb.every_plex_id
else:
func = getattr(plexdb, item_getter)
batch = list(func(plex_type, offset, BATCH_SIZE))
for plex_id in batch:
# Do the actual, time-consuming processing
if self.should_suspend() or self.should_cancel():
raise ProcessingNotDone()
processor(plex_id, plex_type, self.refresh)
if len(batch) < BATCH_SIZE:
break
offset += BATCH_SIZE
def _loop(self):
for plex_type in SUPPORTED_METADATA:
for item_getter, processor in SUPPORTED_METADATA[plex_type]:
if not processing_is_activated(item_getter):
continue
self._process_in_batches(item_getter, processor, plex_type)
def _run(self):
finished = False
while not finished:
try:
self._loop()
except ProcessingNotDone:
finished = False
else:
finished = True
if self.wait_while_suspended():
break
logger.info('MetadataThread finished completely: %s', finished)
self.callback(finished)
def run(self):
logger.info('Starting MetadataThread')
app.APP.register_metadata_thread(self)
try:
self._run()
except Exception:
utils.ERROR(notify=True)
finally:
app.APP.deregister_metadata_thread(self)
class ProcessMetadataTask(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):
if self.plex_type not in SUPPORTED_METADATA:
return
for item_getter, processor in SUPPORTED_METADATA[self.plex_type]:
if self.should_cancel():
# Just don't process this item at all. Next full sync will
# take care of it
return
if not processing_is_activated(item_getter):
continue
processor(self.plex_id, self.plex_type, self.refresh)

View file

@ -0,0 +1,159 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import os
import sys
import xbmcvfs
import xbmcaddon
from ..plex_api import API
from ..kodi_db import KodiVideoDB
from ..plex_db import PlexDB
from .. import itemtypes, plex_functions as PF, utils, variables as v
# Import the existing Kodi add-on metadata.themoviedb.org.python
__ADDON__ = xbmcaddon.Addon(id='metadata.themoviedb.org.python')
__TEMP_PATH__ = os.path.join(__ADDON__.getAddonInfo('path'), 'python', 'lib')
__BASE__ = xbmcvfs.translatePath(__TEMP_PATH__)
sys.path.append(__BASE__)
import tmdbscraper.tmdb as tmdb
logger = logging.getLogger('PLEX.metadata_movies')
PREFER_KODI_COLLECTION_ART = utils.settings('PreferKodiCollectionArt') == 'false'
TMDB_SUPPORTED_IDS = ('tmdb', 'imdb')
def get_tmdb_scraper(settings):
language = settings.getSettingString('language')
certcountry = settings.getSettingString('tmdbcertcountry')
# Simplify this in the future
# See https://github.com/croneter/PlexKodiConnect/issues/1657
search_language = settings.getSettingString('searchlanguage')
if search_language:
return tmdb.TMDBMovieScraper(settings, language, certcountry, search_language)
else:
return tmdb.TMDBMovieScraper(settings, language, certcountry)
def get_tmdb_details(unique_ids):
settings = xbmcaddon.Addon(id='metadata.themoviedb.org.python')
details = get_tmdb_scraper(settings).get_details(unique_ids)
if 'error' in details:
logger.debug('Could not get tmdb details for %s. Error: %s',
unique_ids, details)
return details
def process_trailers(plex_id, plex_type, refresh=False):
done = True
try:
with PlexDB() as plexdb:
db_item = plexdb.item_by_id(plex_id, plex_type)
if not db_item:
logger.error('Could not get Kodi id for %s %s', plex_type, plex_id)
done = False
return
with KodiVideoDB() as kodidb:
trailer = kodidb.get_trailer(db_item['kodi_id'],
db_item['kodi_type'])
if trailer and (trailer.startswith(f'plugin://{v.ADDON_ID}') or
not refresh):
# No need to get a trailer
return
logger.debug('Processing trailer for %s %s', plex_type, plex_id)
xml = PF.GetPlexMetadata(plex_id)
try:
xml[0].attrib
except (TypeError, IndexError, AttributeError):
logger.warn('Could not get metadata for %s. Skipping that %s '
'for now', plex_id, plex_type)
done = False
return
api = API(xml[0])
if (not api.guids or
not [x for x in api.guids if x in TMDB_SUPPORTED_IDS]):
logger.debug('No unique ids found for %s %s, cannot get a trailer',
plex_type, api.title())
return
trailer = get_tmdb_details(api.guids)
trailer = trailer.get('info', {}).get('trailer')
if trailer:
with KodiVideoDB() as kodidb:
kodidb.set_trailer(db_item['kodi_id'],
db_item['kodi_type'],
trailer)
logger.debug('Found a new trailer for %s %s: %s',
plex_type, api.title(), trailer)
else:
logger.debug('No trailer found for %s %s', plex_type, api.title())
finally:
if done is True:
with PlexDB() as plexdb:
plexdb.set_trailer_synced(plex_id, plex_type)
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 = True
try:
artworks = None
with PlexDB() as plexdb:
db_item = plexdb.item_by_id(plex_id, plex_type)
if not db_item:
logger.error('Could not get Kodi id for %s %s', plex_type, plex_id)
done = False
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:
return
xml = PF.GetPlexMetadata(plex_id)
try:
xml[0].attrib
except (TypeError, IndexError, AttributeError):
logger.debug('Could not get metadata for %s %s. Skipping that '
'item for now', plex_type, plex_id)
done = False
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():
logger.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)
finally:
if done is True:
with PlexDB() as plexdb:
plexdb.set_fanart_synced(plex_id, plex_type)

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import xbmc

View file

@ -1,154 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
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
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):
"""
This will potentially take hours!
"""
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):
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)
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

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from Queue import Full
from queue import Full
from . import common, sections
from ..plex_db import PlexDB
@ -41,11 +40,15 @@ class FillMetadataQueue(common.LibrarySyncMixin,
plex_id = int(xml.get('ratingKey'))
checksum = int('{}{}'.format(
plex_id,
xml.get('updatedAt',
xml.get('addedAt', '1541572987')).replace('-', '')))
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)
@ -54,16 +57,14 @@ class FillMetadataQueue(common.LibrarySyncMixin,
'aborting sync now', plex_id)
section.sync_successful = False
break
else:
count += 1
if not do_process_section:
do_process_section = True
self.processing_queue.add_section(section)
LOG.debug('Put section in queue with %s items: %s',
section.number_of_items, section)
# We might have received LESS items from the PMS than anticipated.
# Ensures that our queues finish
LOG.debug('%s items to process for section %s', count, section)
section.number_of_items = count
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():

View file

@ -1,8 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import Queue
import queue
import xbmcgui
@ -80,7 +79,7 @@ class FullSync(common.LibrarySyncMixin, bg.KillableThread):
@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)
get_metadata_queue = queue.Queue(maxsize=BACKLOG_QUEUE_SIZE)
scanner_thread = FillMetadataQueue(self.repair,
section_queue,
get_metadata_queue,
@ -137,7 +136,7 @@ class FullSync(common.LibrarySyncMixin, bg.KillableThread):
LOG.error('Could not entirely process section %s', section)
self.successful = False
def threaded_get_generators(self, kinds, section_queue, all_items):
def threaded_get_generators(self, kinds, section_queue, items):
"""
Getting iterators is costly, so let's do it in a dedicated thread
"""
@ -154,17 +153,28 @@ class FullSync(common.LibrarySyncMixin, bg.KillableThread):
continue
section = sections.get_sync_section(section,
plex_type=kind[0])
if self.repair or all_items:
updated_at = None
else:
updated_at = section.last_sync - UPDATED_AT_SAFETY \
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=None)
last_viewed_at=last_viewed_at)
except RuntimeError:
LOG.error('Sync at least partially unsuccessful!')
LOG.error('Error getting section iterator %s', section)
@ -182,7 +192,7 @@ class FullSync(common.LibrarySyncMixin, bg.KillableThread):
LOG.debug('Exiting threaded_get_generators')
def full_library_sync(self):
section_queue = Queue.Queue()
section_queue = queue.Queue()
processing_queue = bg.ProcessingQueue(maxsize=XML_QUEUE_SIZE)
kinds = [
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_MOVIE),
@ -195,19 +205,42 @@ class FullSync(common.LibrarySyncMixin, bg.KillableThread):
(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, False).start()
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()
@ -218,14 +251,9 @@ class FullSync(common.LibrarySyncMixin, bg.KillableThread):
return
# SYNC PLAYSTATE of ALL items (otherwise we won't pick up on items that
# were set to unwatched). Also mark all items on the PMS to be able
# to delete the ones still in Kodi
# 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')
if app.SYNC.enable_music:
# In order to not delete all your songs again
kinds.extend([
(v.PLEX_TYPE_SONG, v.PLEX_TYPE_ARTIST),
])
# 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
@ -233,7 +261,9 @@ class FullSync(common.LibrarySyncMixin, bg.KillableThread):
self.dialog = None
bg.FunctionAsTask(self.threaded_get_generators,
None,
kinds, section_queue, True).start()
kinds,
section_queue,
items='all').start()
self.processing_loop_playstates(section_queue)
if self.should_cancel() or not self.successful:
return

View file

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from . import common

View file

@ -1,10 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
import urllib
import urllib.request, urllib.parse, urllib.error
import copy
from ..utils import etree
import xml.etree.ElementTree as etree
from .. import variables as v, utils
ICON_PATH = 'special://home/addons/plugin.video.plexkodiconnect/icon.png'
@ -56,7 +55,7 @@ NODE_TYPES = {
{
'mode': 'browseplex',
'key': ('/library/sections/{self.section_id}&%s'
% urllib.urlencode({'sort': 'rating:desc'})),
% urllib.parse.urlencode({'sort': 'rating:desc'})),
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_MOVIE,
@ -84,7 +83,7 @@ NODE_TYPES = {
{
'mode': 'browseplex',
'key': ('/library/sections/{self.section_id}&%s'
% urllib.urlencode({'sort': 'random'})),
% urllib.parse.urlencode({'sort': 'random'})),
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_MOVIE,
@ -154,7 +153,7 @@ NODE_TYPES = {
{
'mode': 'browseplex',
'key': ('/library/sections/{self.section_id}&%s'
% urllib.urlencode({'sort': 'rating:desc'})),
% urllib.parse.urlencode({'sort': 'rating:desc'})),
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_SHOW,
@ -182,7 +181,7 @@ NODE_TYPES = {
{
'mode': 'browseplex',
'key': ('/library/sections/{self.section_id}&%s'
% urllib.urlencode({'sort': 'random'})),
% urllib.parse.urlencode({'sort': 'random'})),
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_SHOW,
@ -192,7 +191,7 @@ NODE_TYPES = {
{
'mode': 'browseplex',
'key': ('/library/sections/{self.section_id}/recentlyViewed&%s'
% urllib.urlencode({'type': v.PLEX_TYPE_NUMBER_FROM_PLEX_TYPE[v.PLEX_TYPE_EPISODE]})),
% urllib.parse.urlencode({'type': v.PLEX_TYPE_NUMBER_FROM_PLEX_TYPE[v.PLEX_TYPE_EPISODE]})),
'section_id': '{self.section_id}'
},
v.CONTENT_TYPE_EPISODE,
@ -236,7 +235,7 @@ def node_pms(section, node_name, args):
else:
folder = False
xml = etree.Element('node',
attrib={'order': unicode(section.order),
attrib={'order': str(section.order),
'type': 'folder' if folder else 'filter'})
etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH
@ -249,7 +248,7 @@ 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),
xml = etree.Element('node', attrib={'order': str(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
@ -270,7 +269,7 @@ def node_ondeck(section, node_name):
def node_recent(section, node_name):
xml = etree.Element('node',
attrib={'order': unicode(section.order),
attrib={'order': str(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
@ -299,7 +298,7 @@ def node_recent(section, node_name):
def node_all(section, node_name):
xml = etree.Element('node', attrib={'order': unicode(section.order),
xml = etree.Element('node', attrib={'order': str(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
@ -316,7 +315,7 @@ def node_all(section, node_name):
def node_recommended(section, node_name):
xml = etree.Element('node', attrib={'order': unicode(section.order),
xml = etree.Element('node', attrib={'order': str(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
@ -337,7 +336,7 @@ def node_recommended(section, node_name):
def node_genres(section, node_name):
xml = etree.Element('node', attrib={'order': unicode(section.order),
xml = etree.Element('node', attrib={'order': str(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
@ -355,7 +354,7 @@ def node_genres(section, node_name):
def node_sets(section, node_name):
xml = etree.Element('node', attrib={'order': unicode(section.order),
xml = etree.Element('node', attrib={'order': str(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
@ -374,7 +373,7 @@ def node_sets(section, node_name):
def node_random(section, node_name):
xml = etree.Element('node', attrib={'order': unicode(section.order),
xml = etree.Element('node', attrib={'order': str(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
@ -392,7 +391,7 @@ def node_random(section, node_name):
def node_lastplayed(section, node_name):
xml = etree.Element('node', attrib={'order': unicode(section.order),
xml = etree.Element('node', attrib={'order': str(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',

View file

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from . import common, sections

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import copy
@ -10,7 +9,7 @@ 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
import xml.etree.ElementTree as etree
LOG = getLogger('PLEX.sync.sections')
@ -21,10 +20,10 @@ 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(
path_ops.copytree(
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!
copy_function=path_ops.shutil.copyfile)
PLAYLISTS_PATH = path_ops.translate_path("special://profile/playlists/video/")
if not path_ops.exists(PLAYLISTS_PATH):
path_ops.makedirs(PLAYLISTS_PATH)
@ -93,21 +92,22 @@ class Section(object):
"'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__
"}}").format(self=self)
def __nonzero__(self):
def __bool__(self):
"""bool(Section) returns True if section_id, name and section_type are set."""
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.
"""
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
@ -236,7 +236,7 @@ class Section(object):
{key: '{self.<Section attribute>}'}
"""
args = copy.deepcopy(args)
for key, value in args.iteritems():
for key, value in args.items():
args[key] = value.format(self=self)
return utils.extend_url('plugin://%s' % v.ADDON_ID, args)
@ -265,7 +265,7 @@ class Section(object):
args = {
'mode': 'browseplex',
'key': '/library/sections/%s' % self.section_id,
'section_id': unicode(self.section_id)
'section_id': str(self.section_id)
}
if not self.sync_to_kodi:
args['synched'] = 'false'
@ -276,7 +276,7 @@ class Section(object):
args = {
'mode': 'browseplex',
'key': '/library/sections/%s/all' % self.section_id,
'section_id': unicode(self.section_id)
'section_id': str(self.section_id)
}
if not self.sync_to_kodi:
args['synched'] = 'false'
@ -318,7 +318,7 @@ class Section(object):
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)})
attrib={'order': str(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')
@ -650,6 +650,9 @@ def _sync_from_pms(pick_libraries):
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():
@ -708,7 +711,7 @@ def _clear_window_vars(index):
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 typus in (x[0] for y in list(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)

View file

@ -1,10 +1,9 @@
#!/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 .additional_metadata import ProcessMetadataTask
from ..plex_api import API
from ..plex_db import PlexDB
from .. import kodi_db
@ -85,9 +84,8 @@ def process_websocket_messages():
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()
if successful:
task = ProcessMetadataTask()
task.setup(message['plex_id'],
message['plex_type'],
refresh=False)
@ -160,7 +158,7 @@ def store_timeline_message(data):
continue
status = int(message['state'])
if typus == 'playlist' and PLAYLIST_SYNC_ENABLED:
playlists.websocket(plex_id=unicode(message['itemID']),
playlists.websocket(plex_id=str(message['itemID']),
status=status)
elif status == 9:
# Immediately and always process deletions (as the PMS will

View file

@ -1,34 +1,17 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
import logging
import xbmc
###############################################################################
LEVELS = {
logging.ERROR: xbmc.LOGERROR,
logging.WARNING: xbmc.LOGWARNING,
logging.INFO: xbmc.LOGNOTICE,
logging.INFO: xbmc.LOGINFO,
logging.DEBUG: xbmc.LOGDEBUG
}
###############################################################################
def try_encode(uniString, encoding='utf-8'):
"""
Will try to encode uniString (in unicode) to encoding. This possibly
fails with e.g. Android TV's Python, which does not accept arguments for
string.encode()
"""
if isinstance(uniString, str):
# already encoded
return uniString
try:
uniString = uniString.encode(encoding, "ignore")
except TypeError:
uniString = uniString.encode()
return uniString
def config():
logger = logging.getLogger('PLEX')
logger.addHandler(LogHandler())
@ -38,13 +21,7 @@ def config():
class LogHandler(logging.StreamHandler):
def __init__(self):
logging.StreamHandler.__init__(self)
self.setFormatter(logging.Formatter(fmt=b"%(name)s: %(message)s"))
self.setFormatter(logging.Formatter(fmt='%(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:
xbmc.log(try_encode(self.format(record)),
level=LEVELS[record.levelno])

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from . import variables as v
@ -23,80 +22,13 @@ def check_migration():
LOG.info('Already migrated to PKC version %s' % v.ADDON_VERSION)
return
if not utils.compare_version(last_migration, '1.8.2'):
LOG.info('Migrating to version 1.8.1')
# Set the new PKC theMovieDB key
utils.settings('themoviedbAPIKey',
value='19c90103adb9e98f2172c6a6a3d85dc4')
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
utils.settings('ipaddress', value='')
utils.settings('port', value='')
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
if not utils.compare_version(last_migration, '3.0.4'):
LOG.info('Migrating to version 3.0.4')
# Add an additional column `trailer_synced` in the Plex movie table
from .plex_db import PlexDB
with PlexDB() as plexdb:
plexdb.cursor.execute('DROP INDEX IF EXISTS ix_playlists_3')
query = 'ALTER TABLE movie ADD trailer_synced BOOLEAN'
plexdb.cursor.execute(query)
# 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,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import re

View file

@ -14,41 +14,25 @@ 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):
def append_os_sep(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)
Appends either a '\\' or '/' - IRRELEVANT of the host OS!! (os.path.join is
dependant on the host OS)
"""
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)
separator = '/' if '/' in path else '\\'
return path if path.endswith(separator) else path + separator
def translate_path(path):
@ -57,8 +41,7 @@ def translate_path(path):
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')
return xbmcvfs.translatePath(path)
def exists(path):
@ -66,7 +49,7 @@ 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
return xbmcvfs.exists(path) == 1
def rmtree(path, *args, **kwargs):
@ -80,12 +63,12 @@ def rmtree(path, *args, **kwargs):
is false and onerror is None, an exception is raised.
"""
return shutil.rmtree(encode_path(path), *args, **kwargs)
return shutil.rmtree(path, *args, **kwargs)
def copyfile(src, dst):
"""Copy data from src to dst"""
return shutil.copyfile(encode_path(src), encode_path(dst))
return shutil.copyfile(src, dst)
def makedirs(path, *args, **kwargs):
@ -95,7 +78,7 @@ def makedirs(path, *args, **kwargs):
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)
return os.makedirs(path, *args, **kwargs)
def remove(path):
@ -107,7 +90,7 @@ def remove(path):
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))
return os.remove(path)
def walk(top, topdown=True, onerror=None, followlinks=False):
@ -170,40 +153,57 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
"""
# Get all the results from os.walk and store them in a list
walker = list(os.walk(encode_path(top),
walker = list(os.walk(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])
yield (top,
[x for x in dirs],
[x for x in nondirs])
def copy_tree(src, dst, *args, **kwargs):
def copytree(src, dst, *args, **kwargs):
"""
Copy an entire directory tree 'src' to a new location 'dst'.
Recursively copy an entire directory tree rooted at src to a directory named
dst and return the destination directory. dirs_exist_ok dictates whether to
raise an exception in case dst or any missing parent directory already
exists.
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'.
Permissions and times of directories are copied with copystat(), individual
files are copied using copy2().
'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'.
If symlinks is true, symbolic links in the source tree are represented as
symbolic links in the new tree and the metadata of the original links will
be copied as far as the platform allows; if false or omitted, the contents
and metadata of the linked files are copied to the new tree.
When symlinks is false, if the file pointed by the symlink doesnt exist, an
exception will be added in the list of errors raised in an Error exception
at the end of the copy process. You can set the optional
ignore_dangling_symlinks flag to true if you want to silence this exception.
Notice that this option has no effect on platforms that dont support
os.symlink().
If ignore is given, it must be a callable that will receive as its arguments
the directory being visited by copytree(), and a list of its contents, as
returned by os.listdir(). Since copytree() is called recursively, the ignore
callable will be called once for each directory that is copied. The callable
must return a sequence of directory and file names relative to the current
directory (i.e. a subset of the items in its second argument); these names
will then be ignored in the copy process. ignore_patterns() can be used to
create such a callable that ignores names based on glob-style patterns.
If exception(s) occur, an Error is raised with a list of reasons.
If copy_function is given, it must be a callable that will be used to copy
each file. It will be called with the source path and the destination path
as arguments. By default, copy2() is used, but any function that supports
the same signature (like copy()) can be used.
Raises an auditing event shutil.copytree with arguments src, dst.
"""
src = encode_path(src)
dst = encode_path(dst)
return dir_util.copy_tree(src, dst, *args, **kwargs)
return shutil.copytree(src, dst, *args, **kwargs)
def basename(path):

View file

@ -38,7 +38,6 @@ Functions
.. autofunction:: real_absolute_path
.. autofunction:: parent_dir_path
"""
import os.path
from functools import partial
@ -72,7 +71,7 @@ def get_dir_walker(recursive, topdown=True, followlinks=False):
try:
yield next(os.walk(path, topdown=topdown, followlinks=followlinks))
except NameError:
yield os.walk(path, topdown=topdown, followlinks=followlinks).next() #IGNORE:E1101
yield next(os.walk(path, topdown=topdown, followlinks=followlinks)) #IGNORE:E1101
return walk

View file

@ -33,7 +33,6 @@ Functions
.. autofunction:: match_path_against
.. autofunction:: filter_paths
"""
from fnmatch import fnmatch, fnmatchcase
__all__ = ['match_path',

View file

@ -0,0 +1,35 @@
"""
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
"""
from .__version__ import __author__, __copyright__, __email__, __license__, __version__
from ._common import (
Platform,
ascii_symbols,
normalize_platform,
replace_unprintable_char,
unprintable_ascii_chars,
validate_null_string,
validate_pathtype,
)
from ._filename import FileNameSanitizer, is_valid_filename, sanitize_filename, validate_filename
from ._filepath import (
FilePathSanitizer,
is_valid_filepath,
sanitize_file_path,
sanitize_filepath,
validate_file_path,
validate_filepath,
)
from ._ltsv import sanitize_ltsv_label, validate_ltsv_label
from ._symbol import replace_symbol, validate_symbol
from .error import (
ErrorReason,
InvalidCharError,
InvalidLengthError,
InvalidReservedNameError,
NullNameError,
ReservedNameError,
ValidationError,
ValidReservedNameError,
)

View file

@ -0,0 +1,6 @@
__author__ = "Tsuyoshi Hombashi"
__copyright__ = "Copyright 2016, {}".format(__author__)
__license__ = "MIT License"
__version__ = "2.4.1"
__maintainer__ = __author__
__email__ = "tsuyoshi.hombashi@gmail.com"

View file

@ -0,0 +1,137 @@
"""
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
"""
import abc
import os
from typing import Optional, Tuple, cast
from ._common import PathType, Platform, PlatformType, normalize_platform, unprintable_ascii_chars
from .error import ReservedNameError, ValidationError
class BaseFile:
_INVALID_PATH_CHARS = "".join(unprintable_ascii_chars)
_INVALID_FILENAME_CHARS = _INVALID_PATH_CHARS + "/"
_INVALID_WIN_PATH_CHARS = _INVALID_PATH_CHARS + ':*?"<>|\t\n\r\x0b\x0c'
_INVALID_WIN_FILENAME_CHARS = _INVALID_FILENAME_CHARS + _INVALID_WIN_PATH_CHARS + "\\"
_ERROR_MSG_TEMPLATE = "invalid char found: invalids=({invalid}), value={value}"
@property
def platform(self) -> Platform:
return self.__platform
@property
def reserved_keywords(self) -> Tuple[str, ...]:
return tuple()
@property
def min_len(self) -> int:
return self._min_len
@property
def max_len(self) -> int:
return self._max_len
def __init__(
self,
min_len: Optional[int],
max_len: Optional[int],
check_reserved: bool,
platform_max_len: Optional[int] = None,
platform: PlatformType = None,
) -> None:
self.__platform = normalize_platform(platform)
self._check_reserved = check_reserved
if min_len is None:
min_len = 1
self._min_len = max(min_len, 1)
if platform_max_len is None:
platform_max_len = self._get_default_max_path_len()
if max_len in [None, -1]:
self._max_len = platform_max_len
else:
self._max_len = cast(int, max_len)
self._max_len = min(self._max_len, platform_max_len)
self._validate_max_len()
def _is_posix(self) -> bool:
return self.platform == Platform.POSIX
def _is_universal(self) -> bool:
return self.platform == Platform.UNIVERSAL
def _is_linux(self) -> bool:
return self.platform == Platform.LINUX
def _is_windows(self) -> bool:
return self.platform == Platform.WINDOWS
def _is_macos(self) -> bool:
return self.platform == Platform.MACOS
def _validate_max_len(self) -> None:
if self.max_len < 1:
raise ValueError("max_len must be greater or equals to one")
if self.min_len > self.max_len:
raise ValueError("min_len must be lower than max_len")
def _get_default_max_path_len(self) -> int:
if self._is_linux():
return 4096
if self._is_windows():
return 260
if self._is_posix() or self._is_macos():
return 1024
return 260 # universal
class AbstractValidator(BaseFile, metaclass=abc.ABCMeta):
@abc.abstractmethod
def validate(self, value: PathType) -> None: # pragma: no cover
pass
def is_valid(self, value: PathType) -> bool:
try:
self.validate(value)
except (TypeError, ValidationError):
return False
return True
def _is_reserved_keyword(self, value: str) -> bool:
return value in self.reserved_keywords
class AbstractSanitizer(BaseFile, metaclass=abc.ABCMeta):
@abc.abstractmethod
def sanitize(self, value: PathType, replacement_text: str = "") -> PathType: # pragma: no cover
pass
class BaseValidator(AbstractValidator):
def _validate_reserved_keywords(self, name: str) -> None:
if not self._check_reserved:
return
root_name = self.__extract_root_name(name)
if self._is_reserved_keyword(root_name.upper()):
raise ReservedNameError(
"'{}' is a reserved name".format(root_name),
reusable_name=False,
reserved_name=root_name,
platform=self.platform,
)
@staticmethod
def __extract_root_name(path: str) -> str:
return os.path.splitext(os.path.basename(path))[0]

View file

@ -0,0 +1,147 @@
"""
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
"""
import enum
import platform
import re
import string
from pathlib import Path
from typing import Any, List, Optional, Union, cast
_re_whitespaces = re.compile(r"^[\s]+$")
@enum.unique
class Platform(enum.Enum):
POSIX = "POSIX"
UNIVERSAL = "universal"
LINUX = "Linux"
WINDOWS = "Windows"
MACOS = "macOS"
PathType = Union[str, Path]
PlatformType = Union[str, Platform, None]
def is_pathlike_obj(value: PathType) -> bool:
return isinstance(value, Path)
def validate_pathtype(
text: PathType, allow_whitespaces: bool = False, error_msg: Optional[str] = None
) -> None:
from .error import ErrorReason, ValidationError
if _is_not_null_string(text) or is_pathlike_obj(text):
return
if allow_whitespaces and _re_whitespaces.search(str(text)):
return
if is_null_string(text):
if not error_msg:
error_msg = "the value must be a not empty"
raise ValidationError(
description=error_msg,
reason=ErrorReason.NULL_NAME,
)
raise TypeError("text must be a string: actual={}".format(type(text)))
def validate_null_string(text: PathType, error_msg: Optional[str] = None) -> None:
# Deprecated: alias to validate_pathtype
validate_pathtype(text, False, error_msg)
def preprocess(name: PathType) -> str:
if is_pathlike_obj(name):
name = str(name)
return cast(str, name)
def is_null_string(value: Any) -> bool:
if value is None:
return True
try:
return len(value.strip()) == 0
except AttributeError:
return False
def _is_not_null_string(value: Any) -> bool:
try:
return len(value.strip()) > 0
except AttributeError:
return False
def _get_unprintable_ascii_chars() -> List[str]:
return [chr(c) for c in range(128) if chr(c) not in string.printable]
unprintable_ascii_chars = tuple(_get_unprintable_ascii_chars())
def _get_ascii_symbols() -> List[str]:
symbol_list = [] # type: List[str]
for i in range(128):
c = chr(i)
if c in unprintable_ascii_chars or c in string.digits + string.ascii_letters:
continue
symbol_list.append(c)
return symbol_list
ascii_symbols = tuple(_get_ascii_symbols())
__RE_UNPRINTABLE_CHARS = re.compile(
"[{}]".format(re.escape("".join(unprintable_ascii_chars))), re.UNICODE
)
def replace_unprintable_char(text: str, replacement_text: str = "") -> str:
try:
return __RE_UNPRINTABLE_CHARS.sub(replacement_text, text)
except (TypeError, AttributeError):
raise TypeError("text must be a string")
def normalize_platform(name: PlatformType) -> Platform:
if isinstance(name, Platform):
return name
if name:
name = name.strip().lower()
if name == "posix":
return Platform.POSIX
if name == "auto":
name = platform.system().lower()
if name in ["linux"]:
return Platform.LINUX
if name and name.startswith("win"):
return Platform.WINDOWS
if name in ["mac", "macos", "darwin"]:
return Platform.MACOS
return Platform.UNIVERSAL
def findall_to_str(match: List[Any]) -> str:
return ", ".join([repr(text) for text in match])

View file

@ -0,0 +1,16 @@
_NTFS_RESERVED_FILE_NAMES = (
"$Mft",
"$MftMirr",
"$LogFile",
"$Volume",
"$AttrDef",
"$Bitmap",
"$Boot",
"$BadClus",
"$Secure",
"$Upcase",
"$Extend",
"$Quota",
"$ObjId",
"$Reparse",
) # Only in root directory

View file

@ -0,0 +1,341 @@
"""
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
"""
import itertools
import ntpath
import posixpath
import re
from pathlib import Path
from typing import Optional, Pattern, Tuple
from ._base import AbstractSanitizer, BaseFile, BaseValidator
from ._common import (
PathType,
Platform,
PlatformType,
findall_to_str,
is_pathlike_obj,
preprocess,
validate_pathtype,
)
from .error import ErrorReason, InvalidCharError, InvalidLengthError, ValidationError
_DEFAULT_MAX_FILENAME_LEN = 255
_RE_INVALID_FILENAME = re.compile(
"[{:s}]".format(re.escape(BaseFile._INVALID_FILENAME_CHARS)), re.UNICODE
)
_RE_INVALID_WIN_FILENAME = re.compile(
"[{:s}]".format(re.escape(BaseFile._INVALID_WIN_FILENAME_CHARS)), re.UNICODE
)
class FileNameSanitizer(AbstractSanitizer):
def __init__(
self,
min_len: Optional[int] = 1,
max_len: Optional[int] = _DEFAULT_MAX_FILENAME_LEN,
platform: PlatformType = None,
check_reserved: bool = True,
) -> None:
super().__init__(
min_len=min_len,
max_len=max_len,
check_reserved=check_reserved,
platform_max_len=_DEFAULT_MAX_FILENAME_LEN,
platform=platform,
)
self._sanitize_regexp = self._get_sanitize_regexp()
self.__validator = FileNameValidator(
min_len=self.min_len,
max_len=self.max_len,
check_reserved=check_reserved,
platform=self.platform,
)
def sanitize(self, value: PathType, replacement_text: str = "") -> PathType:
try:
validate_pathtype(value, allow_whitespaces=True if not self._is_windows() else False)
except ValidationError as e:
if e.reason == ErrorReason.NULL_NAME:
return ""
raise
sanitized_filename = self._sanitize_regexp.sub(replacement_text, str(value))
sanitized_filename = sanitized_filename[: self.max_len]
try:
self.__validator.validate(sanitized_filename)
except ValidationError as e:
if e.reason == ErrorReason.RESERVED_NAME and e.reusable_name is False:
sanitized_filename = re.sub(
re.escape(e.reserved_name), "{}_".format(e.reserved_name), sanitized_filename
)
elif e.reason == ErrorReason.INVALID_CHARACTER:
if self.platform in [Platform.UNIVERSAL, Platform.WINDOWS]:
sanitized_filename = sanitized_filename.rstrip(" .")
if is_pathlike_obj(value):
return Path(sanitized_filename)
return sanitized_filename
def _get_sanitize_regexp(self) -> Pattern:
if self.platform in [Platform.UNIVERSAL, Platform.WINDOWS]:
return _RE_INVALID_WIN_FILENAME
return _RE_INVALID_FILENAME
class FileNameValidator(BaseValidator):
_WINDOWS_RESERVED_FILE_NAMES = ("CON", "PRN", "AUX", "CLOCK$", "NUL") + tuple(
"{:s}{:d}".format(name, num)
for name, num in itertools.product(("COM", "LPT"), range(1, 10))
)
_MACOS_RESERVED_FILE_NAMES = (":",)
@property
def reserved_keywords(self) -> Tuple[str, ...]:
common_keywords = super().reserved_keywords
if self._is_universal():
return (
common_keywords
+ self._WINDOWS_RESERVED_FILE_NAMES
+ self._MACOS_RESERVED_FILE_NAMES
)
if self._is_windows():
return common_keywords + self._WINDOWS_RESERVED_FILE_NAMES
if self._is_posix() or self._is_macos():
return common_keywords + self._MACOS_RESERVED_FILE_NAMES
return common_keywords
def __init__(
self,
min_len: Optional[int] = 1,
max_len: Optional[int] = _DEFAULT_MAX_FILENAME_LEN,
platform: PlatformType = None,
check_reserved: bool = True,
) -> None:
super().__init__(
min_len=min_len,
max_len=max_len,
check_reserved=check_reserved,
platform_max_len=_DEFAULT_MAX_FILENAME_LEN,
platform=platform,
)
def validate(self, value: PathType) -> None:
validate_pathtype(
value,
allow_whitespaces=False
if self.platform in [Platform.UNIVERSAL, Platform.WINDOWS]
else True,
)
unicode_filename = preprocess(value)
value_len = len(unicode_filename)
self.validate_abspath(unicode_filename)
if value_len > self.max_len:
raise InvalidLengthError(
"filename is too long: expected<={:d}, actual={:d}".format(self.max_len, value_len)
)
if value_len < self.min_len:
raise InvalidLengthError(
"filename is too short: expected>={:d}, actual={:d}".format(self.min_len, value_len)
)
self._validate_reserved_keywords(unicode_filename)
if self._is_universal() or self._is_windows():
self.__validate_win_filename(unicode_filename)
else:
self.__validate_unix_filename(unicode_filename)
def validate_abspath(self, value: str) -> None:
err = ValidationError(
description="found an absolute path ({}), expected a filename".format(value),
platform=self.platform,
reason=ErrorReason.FOUND_ABS_PATH,
)
if self._is_universal() or self._is_windows():
if ntpath.isabs(value):
raise err
if posixpath.isabs(value):
raise err
def __validate_unix_filename(self, unicode_filename: str) -> None:
match = _RE_INVALID_FILENAME.findall(unicode_filename)
if match:
raise InvalidCharError(
self._ERROR_MSG_TEMPLATE.format(
invalid=findall_to_str(match), value=repr(unicode_filename)
)
)
def __validate_win_filename(self, unicode_filename: str) -> None:
match = _RE_INVALID_WIN_FILENAME.findall(unicode_filename)
if match:
raise InvalidCharError(
self._ERROR_MSG_TEMPLATE.format(
invalid=findall_to_str(match), value=repr(unicode_filename)
),
platform=Platform.WINDOWS,
)
if unicode_filename in (".", ".."):
return
if unicode_filename[-1] in (" ", "."):
raise InvalidCharError(
self._ERROR_MSG_TEMPLATE.format(
invalid=re.escape(unicode_filename[-1]), value=repr(unicode_filename)
),
platform=Platform.WINDOWS,
description="Do not end a file or directory name with a space or a period",
)
def validate_filename(
filename: PathType,
platform: Optional[str] = None,
min_len: int = 1,
max_len: int = _DEFAULT_MAX_FILENAME_LEN,
check_reserved: bool = True,
) -> None:
"""Verifying whether the ``filename`` is a valid file name or not.
Args:
filename:
Filename to validate.
platform:
Target platform name of the filename.
.. include:: platform.txt
min_len:
Minimum length of the ``filename``. The value must be greater or equal to one.
Defaults to ``1``.
max_len:
Maximum length of the ``filename``. The value must be lower than:
- ``Linux``: 4096
- ``macOS``: 1024
- ``Windows``: 260
- ``universal``: 260
Defaults to ``255``.
check_reserved:
If |True|, check reserved names of the ``platform``.
Raises:
ValidationError (ErrorReason.INVALID_LENGTH):
If the ``filename`` is longer than ``max_len`` characters.
ValidationError (ErrorReason.INVALID_CHARACTER):
If the ``filename`` includes invalid character(s) for a filename:
|invalid_filename_chars|.
The following characters are also invalid for Windows platform:
|invalid_win_filename_chars|.
ValidationError (ErrorReason.RESERVED_NAME):
If the ``filename`` equals reserved name by OS.
Windows reserved name is as follows:
``"CON"``, ``"PRN"``, ``"AUX"``, ``"NUL"``, ``"COM[1-9]"``, ``"LPT[1-9]"``.
Example:
:ref:`example-validate-filename`
See Also:
`Naming Files, Paths, and Namespaces - Win32 apps | Microsoft Docs
<https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file>`__
"""
FileNameValidator(
platform=platform, min_len=min_len, max_len=max_len, check_reserved=check_reserved
).validate(filename)
def is_valid_filename(
filename: PathType,
platform: Optional[str] = None,
min_len: int = 1,
max_len: Optional[int] = None,
check_reserved: bool = True,
) -> bool:
"""Check whether the ``filename`` is a valid name or not.
Args:
filename:
A filename to be checked.
Example:
:ref:`example-is-valid-filename`
See Also:
:py:func:`.validate_filename()`
"""
return FileNameValidator(
platform=platform, min_len=min_len, max_len=max_len, check_reserved=check_reserved
).is_valid(filename)
def sanitize_filename(
filename: PathType,
replacement_text: str = "",
platform: Optional[str] = None,
max_len: Optional[int] = _DEFAULT_MAX_FILENAME_LEN,
check_reserved: bool = True,
) -> PathType:
"""Make a valid filename from a string.
To make a valid filename the function does:
- Replace invalid characters as file names included in the ``filename``
with the ``replacement_text``. Invalid characters are:
- unprintable characters
- |invalid_filename_chars|
- for Windows (or universal) only: |invalid_win_filename_chars|
- Append underscore (``"_"``) at the tail of the name if sanitized name
is one of the reserved names by operating systems
(only when ``check_reserved`` is |True|).
Args:
filename: Filename to sanitize.
replacement_text:
Replacement text for invalid characters. Defaults to ``""``.
platform:
Target platform name of the filename.
.. include:: platform.txt
max_len:
Maximum length of the ``filename`` length. Truncate the name length if
the ``filename`` length exceeds this value.
Defaults to ``255``.
check_reserved:
If |True|, sanitize reserved names of the ``platform``.
Returns:
Same type as the ``filename`` (str or PathLike object):
Sanitized filename.
Raises:
ValueError:
If the ``filename`` is an invalid filename.
Example:
:ref:`example-sanitize-filename`
"""
return FileNameSanitizer(
platform=platform, max_len=max_len, check_reserved=check_reserved
).sanitize(filename, replacement_text)

View file

@ -0,0 +1,427 @@
"""
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
"""
import ntpath
import os.path
import posixpath
import re
from pathlib import Path
from typing import List, Optional, Pattern, Tuple # noqa
from ._base import AbstractSanitizer, BaseFile, BaseValidator
from ._common import (
PathType,
Platform,
PlatformType,
findall_to_str,
is_pathlike_obj,
preprocess,
validate_pathtype,
)
from ._const import _NTFS_RESERVED_FILE_NAMES
from ._filename import FileNameSanitizer, FileNameValidator
from .error import (
ErrorReason,
InvalidCharError,
InvalidLengthError,
ReservedNameError,
ValidationError,
)
_RE_INVALID_PATH = re.compile("[{:s}]".format(re.escape(BaseFile._INVALID_PATH_CHARS)), re.UNICODE)
_RE_INVALID_WIN_PATH = re.compile(
"[{:s}]".format(re.escape(BaseFile._INVALID_WIN_PATH_CHARS)), re.UNICODE
)
class FilePathSanitizer(AbstractSanitizer):
def __init__(
self,
min_len: Optional[int] = 1,
max_len: Optional[int] = None,
platform: PlatformType = None,
check_reserved: bool = True,
normalize: bool = True,
) -> None:
super().__init__(
min_len=min_len,
max_len=max_len,
check_reserved=check_reserved,
platform=platform,
)
self._sanitize_regexp = self._get_sanitize_regexp()
self.__fpath_validator = FilePathValidator(
min_len=self.min_len,
max_len=self.max_len,
check_reserved=check_reserved,
platform=self.platform,
)
self.__fname_sanitizer = FileNameSanitizer(
min_len=self.min_len,
max_len=self.max_len,
check_reserved=check_reserved,
platform=self.platform,
)
self.__normalize = normalize
if self._is_universal() or self._is_windows():
self.__split_drive = ntpath.splitdrive
else:
self.__split_drive = posixpath.splitdrive
def sanitize(self, value: PathType, replacement_text: str = "") -> PathType:
if not value:
return ""
self.__fpath_validator.validate_abspath(value)
unicode_filepath = preprocess(value)
if self.__normalize:
unicode_filepath = os.path.normpath(unicode_filepath)
drive, unicode_filepath = self.__split_drive(unicode_filepath)
sanitized_path = self._sanitize_regexp.sub(replacement_text, unicode_filepath)
if self._is_windows():
path_separator = "\\"
else:
path_separator = "/"
sanitized_entries = [] # type: List[str]
if drive:
sanitized_entries.append(drive)
for entry in sanitized_path.replace("\\", "/").split("/"):
if entry in _NTFS_RESERVED_FILE_NAMES:
sanitized_entries.append("{}_".format(entry))
continue
sanitized_entry = str(self.__fname_sanitizer.sanitize(entry))
if not sanitized_entry:
if not sanitized_entries:
sanitized_entries.append("")
continue
sanitized_entries.append(sanitized_entry)
sanitized_path = path_separator.join(sanitized_entries)
if is_pathlike_obj(value):
return Path(sanitized_path)
return sanitized_path
def _get_sanitize_regexp(self) -> Pattern:
if self.platform in [Platform.UNIVERSAL, Platform.WINDOWS]:
return _RE_INVALID_WIN_PATH
return _RE_INVALID_PATH
class FilePathValidator(BaseValidator):
_RE_NTFS_RESERVED = re.compile(
"|".join("^/{}$".format(re.escape(pattern)) for pattern in _NTFS_RESERVED_FILE_NAMES),
re.IGNORECASE,
)
_MACOS_RESERVED_FILE_PATHS = ("/", ":")
@property
def reserved_keywords(self) -> Tuple[str, ...]:
common_keywords = super().reserved_keywords
if any([self._is_universal(), self._is_posix(), self._is_macos()]):
return common_keywords + self._MACOS_RESERVED_FILE_PATHS
if self._is_linux():
return common_keywords + ("/",)
return common_keywords
def __init__(
self,
min_len: Optional[int] = 1,
max_len: Optional[int] = None,
platform: PlatformType = None,
check_reserved: bool = True,
) -> None:
super().__init__(
min_len=min_len,
max_len=max_len,
check_reserved=check_reserved,
platform=platform,
)
self.__fname_validator = FileNameValidator(
min_len=min_len, max_len=max_len, check_reserved=check_reserved, platform=platform
)
if self._is_universal() or self._is_windows():
self.__split_drive = ntpath.splitdrive
else:
self.__split_drive = posixpath.splitdrive
def validate(self, value: PathType) -> None:
validate_pathtype(
value,
allow_whitespaces=False
if self.platform in [Platform.UNIVERSAL, Platform.WINDOWS]
else True,
)
self.validate_abspath(value)
_drive, value = self.__split_drive(str(value))
if not value:
return
filepath = os.path.normpath(value)
unicode_filepath = preprocess(filepath)
value_len = len(unicode_filepath)
if value_len > self.max_len:
raise InvalidLengthError(
"file path is too long: expected<={:d}, actual={:d}".format(self.max_len, value_len)
)
if value_len < self.min_len:
raise InvalidLengthError(
"file path is too short: expected>={:d}, actual={:d}".format(
self.min_len, value_len
)
)
self._validate_reserved_keywords(unicode_filepath)
unicode_filepath = unicode_filepath.replace("\\", "/")
for entry in unicode_filepath.split("/"):
if not entry or entry in (".", ".."):
continue
self.__fname_validator._validate_reserved_keywords(entry)
if self._is_universal() or self._is_windows():
self.__validate_win_filepath(unicode_filepath)
else:
self.__validate_unix_filepath(unicode_filepath)
def validate_abspath(self, value: PathType) -> None:
value = str(value)
is_posix_abs = posixpath.isabs(value)
is_nt_abs = ntpath.isabs(value)
err_object = ValidationError(
description=(
"an invalid absolute file path ({}) for the platform ({}).".format(
value, self.platform.value
)
+ " to avoid the error, specify an appropriate platform correspond"
+ " with the path format, or 'auto'."
),
platform=self.platform,
reason=ErrorReason.MALFORMED_ABS_PATH,
)
if any([self._is_windows() and is_nt_abs, self._is_linux() and is_posix_abs]):
return
if self._is_universal() and any([is_posix_abs, is_nt_abs]):
ValidationError(
description=(
"{}. expected a platform independent file path".format(
"POSIX absolute file path found"
if is_posix_abs
else "NT absolute file path found"
)
),
platform=self.platform,
reason=ErrorReason.MALFORMED_ABS_PATH,
)
if any([self._is_windows(), self._is_universal()]) and is_posix_abs:
raise err_object
drive, _tail = ntpath.splitdrive(value)
if not self._is_windows() and drive and is_nt_abs:
raise err_object
def __validate_unix_filepath(self, unicode_filepath: str) -> None:
match = _RE_INVALID_PATH.findall(unicode_filepath)
if match:
raise InvalidCharError(
self._ERROR_MSG_TEMPLATE.format(
invalid=findall_to_str(match), value=repr(unicode_filepath)
)
)
def __validate_win_filepath(self, unicode_filepath: str) -> None:
match = _RE_INVALID_WIN_PATH.findall(unicode_filepath)
if match:
raise InvalidCharError(
self._ERROR_MSG_TEMPLATE.format(
invalid=findall_to_str(match), value=repr(unicode_filepath)
),
platform=Platform.WINDOWS,
)
_drive, value = self.__split_drive(unicode_filepath)
if value:
match_reserved = self._RE_NTFS_RESERVED.search(value)
if match_reserved:
reserved_name = match_reserved.group()
raise ReservedNameError(
"'{}' is a reserved name".format(reserved_name),
reusable_name=False,
reserved_name=reserved_name,
platform=self.platform,
)
def validate_filepath(
file_path: PathType,
platform: Optional[str] = None,
min_len: int = 1,
max_len: Optional[int] = None,
check_reserved: bool = True,
) -> None:
"""Verifying whether the ``file_path`` is a valid file path or not.
Args:
file_path:
File path to validate.
platform:
Target platform name of the file path.
.. include:: platform.txt
min_len:
Minimum length of the ``file_path``. The value must be greater or equal to one.
Defaults to ``1``.
max_len:
Maximum length of the ``file_path`` length. If the value is |None|,
automatically determined by the ``platform``:
- ``Linux``: 4096
- ``macOS``: 1024
- ``Windows``: 260
- ``universal``: 260
check_reserved:
If |True|, check reserved names of the ``platform``.
Raises:
ValidationError (ErrorReason.INVALID_CHARACTER):
If the ``file_path`` includes invalid char(s):
|invalid_file_path_chars|.
The following characters are also invalid for Windows platform:
|invalid_win_file_path_chars|
ValidationError (ErrorReason.INVALID_LENGTH):
If the ``file_path`` is longer than ``max_len`` characters.
ValidationError:
If ``file_path`` include invalid values.
Example:
:ref:`example-validate-file-path`
See Also:
`Naming Files, Paths, and Namespaces - Win32 apps | Microsoft Docs
<https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file>`__
"""
FilePathValidator(
platform=platform, min_len=min_len, max_len=max_len, check_reserved=check_reserved
).validate(file_path)
def validate_file_path(file_path, platform=None, max_path_len=None):
# Deprecated
validate_filepath(file_path, platform, max_path_len)
def is_valid_filepath(
file_path: PathType,
platform: Optional[str] = None,
min_len: int = 1,
max_len: Optional[int] = None,
check_reserved: bool = True,
) -> bool:
"""Check whether the ``file_path`` is a valid name or not.
Args:
file_path:
A filepath to be checked.
Example:
:ref:`example-is-valid-filepath`
See Also:
:py:func:`.validate_filepath()`
"""
return FilePathValidator(
platform=platform, min_len=min_len, max_len=max_len, check_reserved=check_reserved
).is_valid(file_path)
def sanitize_filepath(
file_path: PathType,
replacement_text: str = "",
platform: Optional[str] = None,
max_len: Optional[int] = None,
check_reserved: bool = True,
normalize: bool = True,
) -> PathType:
"""Make a valid file path from a string.
To make a valid file path the function does:
- replace invalid characters for a file path within the ``file_path``
with the ``replacement_text``. Invalid characters are as follows:
- unprintable characters
- |invalid_file_path_chars|
- for Windows (or universal) only: |invalid_win_file_path_chars|
- Append underscore (``"_"``) at the tail of the name if sanitized name
is one of the reserved names by operating systems
(only when ``check_reserved`` is |True|).
Args:
file_path:
File path to sanitize.
replacement_text:
Replacement text for invalid characters.
Defaults to ``""``.
platform:
Target platform name of the file path.
.. include:: platform.txt
max_len:
Maximum length of the ``file_path`` length. Truncate the name if the ``file_path``
length exceedd this value. If the value is |None|,
``max_len`` will automatically determined by the ``platform``:
- ``Linux``: 4096
- ``macOS``: 1024
- ``Windows``: 260
- ``universal``: 260
check_reserved:
If |True|, sanitize reserved names of the ``platform``.
normalize:
If |True|, normalize the the file path.
Returns:
Same type as the argument (str or PathLike object):
Sanitized filepath.
Raises:
ValueError:
If the ``file_path`` is an invalid file path.
Example:
:ref:`example-sanitize-file-path`
"""
return FilePathSanitizer(
platform=platform, max_len=max_len, check_reserved=check_reserved, normalize=normalize
).sanitize(file_path, replacement_text)
def sanitize_file_path(file_path, replacement_text="", platform=None, max_path_len=None):
# Deprecated
return sanitize_filepath(file_path, platform, max_path_len)

View file

@ -0,0 +1,45 @@
"""
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
"""
import re
from ._common import preprocess, validate_pathtype
from .error import InvalidCharError
__RE_INVALID_LTSV_LABEL = re.compile("[^0-9A-Za-z_.-]", re.UNICODE)
def validate_ltsv_label(label: str) -> None:
"""
Verifying whether ``label`` is a valid
`Labeled Tab-separated Values (LTSV) <http://ltsv.org/>`__ label or not.
:param label: Label to validate.
:raises pathvalidate.ValidationError:
If invalid character(s) found in the ``label`` for a LTSV format label.
"""
validate_pathtype(label, allow_whitespaces=False, error_msg="label is empty")
match_list = __RE_INVALID_LTSV_LABEL.findall(preprocess(label))
if match_list:
raise InvalidCharError(
"invalid character found for a LTSV format label: {}".format(match_list)
)
def sanitize_ltsv_label(label: str, replacement_text: str = "") -> str:
"""
Replace all of the symbols in text.
:param label: Input text.
:param replacement_text: Replacement text.
:return: A replacement string.
:rtype: str
"""
validate_pathtype(label, allow_whitespaces=False, error_msg="label is empty")
return __RE_INVALID_LTSV_LABEL.sub(replacement_text, preprocess(label))

View file

@ -0,0 +1,110 @@
"""
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
"""
import re
from typing import Sequence
from ._common import ascii_symbols, preprocess, unprintable_ascii_chars
from .error import InvalidCharError
__RE_UNPRINTABLE = re.compile(
"[{}]".format(re.escape("".join(unprintable_ascii_chars))), re.UNICODE
)
__RE_SYMBOL = re.compile(
"[{}]".format(re.escape("".join(ascii_symbols + unprintable_ascii_chars))), re.UNICODE
)
def validate_unprintable(text: str) -> None:
# deprecated
match_list = __RE_UNPRINTABLE.findall(preprocess(text))
if match_list:
raise InvalidCharError("unprintable character found: {}".format(match_list))
def replace_unprintable(text: str, replacement_text: str = "") -> str:
# deprecated
try:
return __RE_UNPRINTABLE.sub(replacement_text, preprocess(text))
except (TypeError, AttributeError):
raise TypeError("text must be a string")
def validate_symbol(text: str) -> None:
"""
Verifying whether symbol(s) included in the ``text`` or not.
Args:
text:
Input text to validate.
Raises:
ValidationError (ErrorReason.INVALID_CHARACTER):
If symbol(s) included in the ``text``.
"""
match_list = __RE_SYMBOL.findall(preprocess(text))
if match_list:
raise InvalidCharError("invalid symbols found: {}".format(match_list))
def replace_symbol(
text: str,
replacement_text: str = "",
exclude_symbols: Sequence[str] = [],
is_replace_consecutive_chars: bool = False,
is_strip: bool = False,
) -> str:
"""
Replace all of the symbols in the ``text``.
Args:
text:
Input text.
replacement_text:
Replacement text.
exclude_symbols:
Symbols that exclude from the replacement.
is_replace_consecutive_chars:
If |True|, replace consecutive multiple ``replacement_text`` characters
to a single character.
is_strip:
If |True|, strip ``replacement_text`` from the beginning/end of the replacement text.
Returns:
A replacement string.
Example:
:ref:`example-sanitize-symbol`
"""
if exclude_symbols:
regexp = re.compile(
"[{}]".format(
re.escape(
"".join(set(ascii_symbols + unprintable_ascii_chars) - set(exclude_symbols))
)
),
re.UNICODE,
)
else:
regexp = __RE_SYMBOL
try:
new_text = regexp.sub(replacement_text, preprocess(text))
except TypeError:
raise TypeError("text must be a string")
if not replacement_text:
return new_text
if is_replace_consecutive_chars:
new_text = re.sub("{}+".format(re.escape(replacement_text)), replacement_text, new_text)
if is_strip:
new_text = new_text.strip(replacement_text)
return new_text

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