Merge pull request #1482 from croneter/python3-beta
Bump python3 branch
This commit is contained in:
commit
84d677b7a2
54 changed files with 7237 additions and 958 deletions
|
@ -81,6 +81,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!
|
||||
|
|
913
addon.xml
913
addon.xml
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="3.1.0" provider-name="croneter">
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="3.2.0" provider-name="croneter">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="3.0.0"/>
|
||||
<import addon="script.module.requests" version="2.22.0+matrix.1" />
|
||||
|
@ -83,13 +83,41 @@
|
|||
<summary lang="uk_UA">Нативна інтеграція Plex в Kodi</summary>
|
||||
<description lang="uk_UA">Підключає Kodi до серверу Plex. Цей плагін передбачає, що ви керуєте всіма своїми відео за допомогою Plex (і ніяк не Kodi). Ви можете втратити дані, які вже зберігаються у відео та музичних БД Kodi (оскільки цей плагін безпосередньо їх змінює). Використовуйте на свій страх і ризик!</description>
|
||||
<disclaimer lang="uk_UA">Використовуйте на свій ризик</disclaimer>
|
||||
<disclaimer lang="lv_LV">Lieto uz savu atbildību</disclaimer>
|
||||
<summary lang="sv_SE">Inbyggd integrering av Plex i Kodi</summary>
|
||||
<description lang="sv_SE">Anslut Kodi till din Plex Media Server. Detta tillägg antar att du hanterar alla dina filmer med Plex (och ingen med Kodi). Du kan förlora data redan sparad i Kodis video och musik databaser (eftersom detta tillägg direkt ändrar dem). Använd på egen risk!</description>
|
||||
<disclaimer lang="sv_SE">Använd på egen risk</disclaimer>
|
||||
<summary lang="lt_LT">Natūralioji „Plex“ integracija į „Kodi“</summary>
|
||||
<description lang="lt_LT">Prijunkite „Kodi“ prie „Plex Medija Serverio“. Šiame papildinyje daroma prielaida, kad valdote visus savo vaizdo įrašus naudodami „Plex“ (ir nė vieno su „Kodi“). Galite prarasti jau saugomus „Kodi“ vaizdo įrašų ir muzikos duomenų bazių duomenis (kadangi šis papildinys juos tiesiogiai pakeičia). Naudokite savo pačių rizika!</description>
|
||||
<disclaimer lang="lt_LT">Naudokite savo pačių rizika</disclaimer>
|
||||
<news>version 3.1.0:
|
||||
<summary lang="ko_KR">Plex를 Kodi에 기본 통합</summary>
|
||||
<description lang="ko_KR">Kodi를 Plex Media Server에 연결합니다. 이 플러그인은 Plex로 모든 비디오를 관리하고 Kodi로는 관리하지 않는다고 가정합니다. Kodi 비디오 및 음악 데이터베이스에 이미 저장된 데이터가 손실 될 수 있습니다 (이 플러그인이 직접 변경하므로). 자신의 책임하에 사용하십시오!</description>
|
||||
<disclaimer lang="ko_KR">자신의 책임하에 사용</disclaimer>
|
||||
<news>version 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
|
||||
|
||||
|
@ -163,885 +191,6 @@ version 3.0.1:
|
|||
|
||||
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
|
||||
- Fix rare playback progress report failing and KeyError: u'containerKey'
|
||||
- Fix rare KeyError: None when trying to sync playlists
|
||||
- Fix TypeError when canceling Plex sync section dialog
|
||||
|
||||
version 2.12.4 (beta only):
|
||||
- Hopefully fix freeze during sync: Don't assign multiple sets/collections for a specific movie
|
||||
- Support metadata provider ids (e.g. for IMDB) for the new Plex Movie Agent
|
||||
|
||||
version 2.12.3:
|
||||
- Fix playback failing due to caching of subtitles with non-ascii chars
|
||||
- Fix ValueError: invalid literal for int() with base 10 during show sync
|
||||
- Fix UnboundLocalError when certain Plex sections are deleted or being un-synched
|
||||
- New method to install PlexKodiConnect directly via an URL. You thus do not need to upload a ZIP file to Kodi anymore.
|
||||
|
||||
version 2.12.2:
|
||||
- version 2.12.0 and 2.12.1 for everyone
|
||||
- Fix regression: sync dialog not showing up when it should
|
||||
|
||||
version 2.12.1 (beta only):
|
||||
- Fix PKC shutdown on Kodi profile switch
|
||||
- Fix Kodi content type for images/photos
|
||||
- Added support for custom set of safe characters when escaping paths (thanks @geropan)
|
||||
- Revert "Don't allow spaces in devicename"
|
||||
- Fix sync dialog showing in certain cases even though user opted out
|
||||
|
||||
version 2.12.0 (beta only):
|
||||
- Fix websocket threads; enable PKC background sync for all Plex Home users!
|
||||
- Fix PKC incorrectly marking a video as unwatched if an external player has been used
|
||||
- Update translations
|
||||
|
||||
version 2.11.7:
|
||||
- Fix PKC crashing on devices running Microsoft UWP, e.g. XBox
|
||||
|
||||
version 2.11.6:
|
||||
- Fix rare sync crash when queue was full
|
||||
- Set "Auto-adjust transcoding quality" to false by default
|
||||
|
||||
version 2.11.5:
|
||||
- Versions 2.11.0-2.11.4 for everyone
|
||||
|
||||
version 2.11.4 (beta only):
|
||||
- Fix another TypeError: 'NoneType' object has no attribute '__getitem__', e.g. when trying to play trailers
|
||||
|
||||
version 2.11.3 (beta only):
|
||||
- Fix TypeError: 'NoneType' object has no attribute '__getitem__', e.g. when displaying albums
|
||||
|
||||
version 2.11.2 (beta only):
|
||||
- Refactor direct and add-on paths. Enables use of Plex music playlists synched to Kodi
|
||||
|
||||
version 2.11.1 (beta only):
|
||||
- Rewire the set-up of audio and subtitle streams, esp. before starting a transcoding session. Fixes playback not starting at all
|
||||
|
||||
version 2.11.0 (beta only):
|
||||
- Fix PKC not burning in (and thus not showing) subtitles when transcoding
|
||||
- When transcoding, only let user choose to burn-in subtitles that can't be displayed otherwise by Kodi
|
||||
- Improve PKC automatically connecting to local PMS
|
||||
- Ensure that our only video transcoding target is h264
|
||||
- Fix adjusted subtitle size not working when burning in subtitles
|
||||
- Fix regression: burn-in subtitles picking up the last user setting instead of the current one
|
||||
|
||||
version 2.10.12:
|
||||
- versions 2.10.5-11 for everyone
|
||||
|
||||
version 2.10.11 (beta only):
|
||||
- Fix yet another rare but annoying bug where PKC becomes unresponsive during sync
|
||||
|
||||
version 2.10.10 (beta only):
|
||||
- Fix rare but annoying bug where PKC becomes unresponsive during sync
|
||||
- Fix PKC background sync not working in some cases
|
||||
|
||||
version 2.10.9 (beta only):
|
||||
- Other Kodi add-ons can now search for Plex items using plugin://plugin.video.plexkodiconnect?mode=search&query=YOUR SEARCH STRING HERE
|
||||
|
||||
version 2.10.8 (beta only):
|
||||
- Improve thread pool management to render PKC snappier
|
||||
- Attempt to fix broken pipe error
|
||||
- Fix DirectPaths when a video's folder name is identical to a video's filename (you will need to manually reset the Kodi database)
|
||||
|
||||
version 2.10.7 (beta only):
|
||||
- Fix PKC not starting up on iOS
|
||||
- Optimize the new sync process and fix some bugs that were introduced
|
||||
- Fix PKC becoming unresponsive e.g. when switching the PMS
|
||||
|
||||
version 2.10.6 (beta only):
|
||||
- Fix AttributeError if user enters an invalid pin code
|
||||
- Fix OperationalError when starting with a fresh PKC installation
|
||||
- Fix IndexError
|
||||
|
||||
version 2.10.5 (beta only):
|
||||
- Rewire library sync to speed it up and fix sync getting stuck in rare cases
|
||||
- Optimize threads by using events instead of a polling mechanism. Fixes PKC becoming unresponsive, e.g. when switching users
|
||||
- Optimize adding values to Kodi databases by not using sqlite COALESCE command
|
||||
- Fix OperationalError when resetting PKC
|
||||
- Improve sync resiliance when certain items are not to be synced to Kodi or PKC skipped an item in the past
|
||||
- Make sure bool is returned instead of an int
|
||||
- Don't use WAL mode for sqlite connections, it is not making any difference
|
||||
|
||||
version 2.10.4:
|
||||
- version 2.10.3 for everyone
|
||||
- Fix to correctly wipe Kodi databases
|
||||
|
||||
version 2.10.3 (beta only):
|
||||
- Fix a couple of issues with music when using direct paths: correctly escape music paths for Kodi regex matching
|
||||
- Fix Recently Added Albums sort order (you will have to reset the Kodi database manually)
|
||||
- Fix database being locked in rare cases
|
||||
- Increase batch size for library sync from 500 to 2000 to increase sync speed
|
||||
- Optimize some code
|
||||
- Fix KeyError when using Plex search capabilities
|
||||
- Check faster for available Plex Media Server to connect to
|
||||
|
||||
version 2.10.2:
|
||||
- Fix Kodi playback jumping to the beginning of a video that just started
|
||||
- Fix transcoding quality degenerating quickly while playing with a new setting to deactivate auto quality for transcoding (applicable e.g. for Chromecast)
|
||||
- Update translations
|
||||
|
||||
version 2.10.1:
|
||||
- Fix resume for Kodi on low powered devices, e.g. Raspberry Pi
|
||||
- Fix resume when using an external player
|
||||
- Fix UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal
|
||||
|
||||
version 2.10.0:
|
||||
- version 2.9.12 - 2.9.14 for everyone
|
||||
- Get rid of some obsolete code for the ContextMonitor we dropped
|
||||
|
||||
version 2.9.14 (beta only):
|
||||
- Fix resume when starting playback via PMS or when force transcoding
|
||||
- Get rid of ContextMonitor and the dedicated Python thread - with new resume mechanics, this is not needed anymore
|
||||
- Optimize clean-up of file table in the Kodi video database after stopping playback
|
||||
- Get rid of some obsolete imports
|
||||
|
||||
version 2.9.13 (beta only):
|
||||
- Fix PKC resuming instead of playing from the beginning
|
||||
|
||||
version 2.9.12 (beta only):
|
||||
- Fix resume not working in some cases
|
||||
- Support Plex search across all media and Plex Media Servers: Navigate to the PlexKodiConnect Add-on, then "Search"
|
||||
- Always use the current Kodi language when communicating with the PMS (restart Kodi when changing the language!)
|
||||
- Fix Kodi crashing when casting from e.g. Plex Web or Plex for Windows
|
||||
- Fix PKC throwing error if m3u playlist contains resume information
|
||||
|
||||
version 2.9.11:
|
||||
- version 2.9.10 for everyone
|
||||
|
||||
version 2.9.10 (beta only):
|
||||
- Add tmdb provider sync
|
||||
- Fix external subtitles not being available
|
||||
- Fix PKC increasing the Plex watch count by 2 instead of 1
|
||||
- Improve subtitle naming
|
||||
- Delete temporary subtitles on playback stop
|
||||
- Fix a missleading string
|
||||
|
||||
version 2.9.9:
|
||||
- Versions 2.9.6 - 2.9.8 for everyone
|
||||
|
||||
version 2.9.8 (beta only):
|
||||
- Fix Play Error in scenarios (older PMS version?) where posting playqueues using an uri `server://` is not possible and `library://` is necessary
|
||||
- Fix rare AttributeError on PKC startup when modifying advancedsettings.xml
|
||||
- Update translations
|
||||
|
||||
version 2.9.7 (beta only):
|
||||
- Correctly escape URLs for Direct Paths
|
||||
- Update settings to inform user that reboot is necessary
|
||||
- Optimize code
|
||||
- Don't migrate PKC settings if we're dealing with a clean new PKC installation
|
||||
- Force-scan every single item in the library - seems like we could lose some recently added items otherwise when updating PKC
|
||||
|
||||
version 2.9.6 (beta only):
|
||||
- Rework logic for using direct paths, direct play, direct streaming and transcoding, using the PMS StreamingBrain: Let PMS StreamingBrain decide on whether we need to force-transcode, New setting to choose "Direct Streaming", Allow for 4k transcoding and direct streaming, New setting to force transcode only 4K and above
|
||||
- Fix PKC background sync synching items to Kodi even though entire section should not be synched
|
||||
- Force a full sync of all items after choosing a new PMS, changing a PMS' address and changing which Plex libraries to sync
|
||||
- Only enforce advancedsettings.xml 'cleanonupdate' to be false for PKC add-on paths
|
||||
- Never give up trying to connect to the PMS or Alexa using websockets
|
||||
- Fix resume when force-transcoding
|
||||
|
||||
version 2.9.5:
|
||||
- Version 2.9.4 for everyone
|
||||
|
||||
version 2.9.4 (beta only):
|
||||
- Fix extras not playing when path substitution is enabled
|
||||
- Fix Plex Companion device restarting playback when reconnecting to PKC
|
||||
- Fix playback report not working after having played a non-Plex video file
|
||||
- Change how items are added to Plex playqueues by using PMS machine identifier
|
||||
- Optimize code for playqueue items
|
||||
- Fix rare AttributeError when shutting down Kodi
|
||||
|
||||
version 2.9.3:
|
||||
- version 2.9.2 for everyone
|
||||
|
||||
version 2.9.2 (beta only):
|
||||
- Fix Plex Companion casting from iOS and Android
|
||||
- Faster sync of playlists
|
||||
- Sync playlists immediately after synching new/changed items and show an info dialog
|
||||
- Fix potential playlist sync issues if there is a dot in the playlist name
|
||||
- Correctly detect whether we already synched a Kodi playlist
|
||||
- Remove obsolete check if path is indeed in unicode
|
||||
- Add unicode representation to Playlist() class
|
||||
- Separate function to wipe all synched Plex playlists
|
||||
- Less logging when comparing PKC versions
|
||||
|
||||
version 2.9.1:
|
||||
- Fix On Deck and Recently Added Episodes for shows not appending showname and season and episode number
|
||||
|
||||
version 2.9.0:
|
||||
WARNING: You might have to manually select your PKC widgets again
|
||||
- versions 2.8.8 - 2.8.11 for everyone
|
||||
- Fix AttributeError: 'NoneType' object has no attribute 'attrib' on playback startup
|
||||
- Add new Lithuanian translations (thanks @egidusm)
|
||||
|
||||
version 2.8.11 (beta only):
|
||||
- Support for the Up Next Kodi add-on
|
||||
- Fix casting to PlexKodiConnect always starting the first episode
|
||||
- Rename video nodes for ondeck
|
||||
|
||||
version 2.8.10 (beta only):
|
||||
- Fix broken PKC update
|
||||
|
||||
version 2.8.9 (beta only):
|
||||
- Fix sections that are not synced not displaying menu but entire library
|
||||
- Provide more metadata for unsynced directory-like items like a tv show
|
||||
- Fix 'Plex.nodes."id".path' not linking directly to entire library
|
||||
|
||||
version 2.8.8 (beta only):
|
||||
WARNING: You might have to manually select your PKC widgets again
|
||||
- Ensure correct Kodi Container.Type is set for PKC widgets
|
||||
- Fix missing cast artwork if an actor also acted as director or writer for another movie. You will have to manually reset the Kodi DB.
|
||||
|
||||
version 2.8.7:
|
||||
- Fix PKC potentially marking a video as watched on startup; don't sync time by toggling a video watch status but use PMS epoch time
|
||||
|
||||
version 2.8.6:
|
||||
- Fix PKC creating thousands of playlists if a single Kodi playlist wasn't unique
|
||||
- Fix FutureWarning
|
||||
|
||||
version 2.8.5:
|
||||
- Fix Trakt add-on not recognizing id of tv shows (you will need to manually reset the Kodi database in the PKC settings under Advanced)
|
||||
- Update translations
|
||||
|
||||
version 2.8.4:
|
||||
- Fix for Kodi 17 Krypton TypeError on playback start: 'offscreen' is an invalid keyword argument for this function
|
||||
- Fix widgets not being populated after very first PlexKodiConnect library sync without a restart of Kodi
|
||||
- Don't restart Kodi if user chose to enter PKC settings on install
|
||||
|
||||
version 2.8.3:
|
||||
- Versions 2.8.1-2.8.2 for everyone
|
||||
|
||||
version 2.8.2 (beta only):
|
||||
- Add an additional, faster On Deck node for movies (for tv shows, this is impossible, unfortunately)
|
||||
- Introduce limits to the number of videos shown in PKC widgets to speed them up
|
||||
- Fix TypeError for Direct Paths: init() got an unexpected keyword argument ‘item’
|
||||
- Fix In Progress widgets being broken and tv shows showing up as completely watched
|
||||
- Update translations
|
||||
|
||||
version 2.8.1 (beta only):
|
||||
- Fix playback startup and RuntimeError: Unknown exception thrown from the call "XBMCAddon::xbmcplugin::setResolvedUrl"
|
||||
- Refactor Plex API
|
||||
- Fix TV Show clearlogo not displaying during playback
|
||||
- Fix rare UnicodeDecodeError on library sync
|
||||
- Add additional info dialog for PKC synching playlists
|
||||
- Update translations
|
||||
|
||||
version 2.8.0:
|
||||
- Finally fix Kodi crashing on playback startup for add-on paths!
|
||||
- All the good stuff from 2.7.15-2.7.18 for everyone
|
||||
|
||||
version 2.7.18 (beta only):
|
||||
- Fix Kodi always playing the same file version of a video if several are present
|
||||
- Also play trailers if user chose to resume movie from the beginning
|
||||
- Ask user whether to resume if using Direct Paths and user initiated playback via PMS
|
||||
- Fix video thrown by Plex Companion not resuming
|
||||
|
||||
version 2.7.17 (beta only):
|
||||
- Another attempt to keep Kodi from crashing on playback startup
|
||||
|
||||
version 2.7.16 (beta only):
|
||||
- Hopefully fix Kodi crashing on playback startup for good
|
||||
|
||||
version 2.7.15 (beta only):
|
||||
- Hopefully fix Kodi crashing on playback startup
|
||||
- Refresh widgets only on homescreen to prevent cursor from jumping within libraries
|
||||
- Don't refresh container when user chose to delete or refresh an item from the context menu
|
||||
|
||||
version 2.7.14:
|
||||
- Correctly clear window variables e.g. on user switch
|
||||
- Reload skin on resetting PKC video nodes
|
||||
- Fix last-played node value to ensure a playcount greater than zero
|
||||
- 2.7.11-2.7.13 for everyone
|
||||
|
||||
version 2.7.13 (beta only):
|
||||
- Fix transcoding not working
|
||||
- Fix 4k H265 not being transcoded
|
||||
- Fix some appearance tweak settings
|
||||
- Fix music and picture nodes pointing to video library
|
||||
- Fix unequality when comparing sections
|
||||
- Fix Plex Companion logging error messages
|
||||
|
||||
version 2.7.12 (beta only):
|
||||
- Fix UnicodeEncodeError on playback startup for direct paths
|
||||
- Attempt to fix rare Kodi crash on PKC exit
|
||||
|
||||
version 2.7.11 (beta only):
|
||||
- Fixes to unicode
|
||||
- Cleanup code, remove some obsolet methods and functions
|
||||
- Fix FutureWarning
|
||||
|
||||
version 2.7.10:
|
||||
- Fix duplicate music entries for direct paths (you will need to manually reset the Kodi database)
|
||||
- Fix UnicodeEncodeError for Direct Paths and some PKC video nodes
|
||||
- Fix playback sometimes not being reported for direct paths
|
||||
- Update translations
|
||||
|
||||
version 2.7.9:
|
||||
- Wait for PKC to authorize before loading widgets
|
||||
- Fix UnicodeDecodeError for libraries with non-ASCII paths
|
||||
- Fix TypeError on Kodi start
|
||||
- Fix Kodi Masterlock for nfs paths (requires restart)
|
||||
|
||||
version 2.7.8:
|
||||
- Fix widgets not working in some cases like NVidia Shield
|
||||
- Fix appending of show title, season and episode number
|
||||
- Fix node paths for skins
|
||||
|
||||
version 2.7.7:
|
||||
- Fix sync not working due to non-ASCII Plex library names
|
||||
- Fix PKC synching playstate to wrong user on profile switch. Be aware that Kodi profile switches are error-prone
|
||||
- Fix playback sometimes not being reported for direct paths
|
||||
- Fix float() argument must be a string or a number
|
||||
- Fix nodes for skin use
|
||||
- Fix 'NoneType' object has no attribute 'kodi_path'
|
||||
|
||||
version 2.7.6:
|
||||
- Make 2.7.5 available for everyone
|
||||
|
||||
version 2.7.5:
|
||||
- Giant overhaul of widgets
|
||||
- Fix some KeyErrors when playing songs
|
||||
- Fix rare cases where playlists were being created
|
||||
|
||||
version 2.7.4:
|
||||
- Fix PKC not synching new items if an older Kodi db is present
|
||||
|
||||
version 2.7.3:
|
||||
- Fix PKC trying to initialize playqueues over and over again
|
||||
- Fix PKC not starting due to a higher version Kodi database
|
||||
|
||||
version 2.7.2:
|
||||
- Fix Kodi profile switch not working correctly and PKC not exiting cleanly
|
||||
|
||||
version 2.7.1:
|
||||
- Fix playback not starting at all
|
||||
- Fix rare TypeError: unsupported operand type(s) for /: 'NoneType' and 'int' on playback startup
|
||||
- Improve plex db lookups by creating better db indicees
|
||||
- Fix background sync crashing in rare cases
|
||||
- Update translations
|
||||
- Add Ko-fi donate button
|
||||
|
||||
version 2.7.0:
|
||||
- WARNING: You will need to reset the Kodi database if you're using the stable version of PKC!
|
||||
- Version 2.6.6-9 for everyone
|
||||
- Choose which Plex libraries get synched to Kodi
|
||||
|
||||
version 2.6.9 (beta only):
|
||||
- Fix PKC crashing on resetting the database
|
||||
|
||||
version 2.6.8 (beta only):
|
||||
- Choose which Plex libraries get synched to Kodi
|
||||
- Fix PKC becoming unresponsive
|
||||
- Fix rare case where thousands of identical playlists could be generated
|
||||
- Fix movies or shows disappearing in fringe cases
|
||||
- Fix processing of collections in special cases
|
||||
- Implement Codacy suggestions
|
||||
|
||||
version 2.6.7 (beta only):
|
||||
- Fix "Unauthorized for PMS" e.g. on switching Plex users
|
||||
- Improve error messages when playback failes
|
||||
|
||||
version 2.6.6 (beta only):
|
||||
- WARNING: You will need to reset the Kodi database!
|
||||
- Greatly speed up sync for episodes, especially for large libraries
|
||||
- Allow websocket redirects. Never allow insecure HTTPs connections for Kodi Leia
|
||||
- Optimize headers for communication with PMS to appear like a Plex Media Player
|
||||
- Fix PMS log entries 'Unable to find client profile for device'
|
||||
- Improve sync dialog
|
||||
|
||||
version 2.6.5:
|
||||
- Fix extras not playing
|
||||
- Hide "Verify SSL certificate" setting for Kodi 18 Krypton
|
||||
- Improve logging
|
||||
- Update translations
|
||||
|
||||
version 2.6.4:
|
||||
- Fix music items getting deleted on startup
|
||||
- Never ignore SSL certificate errors for Kodi >= 18 - just like Kodi
|
||||
- Fix playback not starting at the beginning
|
||||
- Improve dialog to manually enter PMS IP and port
|
||||
- Show logged in Plex home user in the settings and allow changing it
|
||||
- Update German strings
|
||||
- Implement Codacy suggestions
|
||||
|
||||
version 2.6.3:
|
||||
- Fix PKC crashing on Xbox
|
||||
|
||||
version 2.6.2:
|
||||
- Fix playlist sync: sequence item 0: expected string or unicode
|
||||
- Fix PKC not deleting all the items it should
|
||||
- Fix keyError 'sessionKey' for weird PMS messages
|
||||
- Fix artwork caching AttributeError: 'ImageCachingThread' object has no attribute 'cancel'
|
||||
- Improve pop-up "Searching for PMS"
|
||||
- Fix FutureWarning
|
||||
|
||||
version 2.6.1:
|
||||
- WARNING: You will need to reset the Kodi database!
|
||||
- Fix TV sections not being deleted e.g. after user switch
|
||||
- Don't show a library sync error pop-up when full sync is interrupted
|
||||
- Fix to correctly escape paths
|
||||
- Update translations
|
||||
|
||||
version 2.6.0:
|
||||
- Support for Kodi 18 Leia
|
||||
- Big overhaul of the synching process, it's now much faster
|
||||
- PKC now supports really big Plex and Kodi libraries
|
||||
- Too many other improvements to recount. See the changelog for the 2.5.x versions
|
||||
Furthermore:
|
||||
- Don't lock Plex DB when processing websocket messages
|
||||
- Fix KeyError: u'kodi_fileid' for some Plex websocket messages
|
||||
- Update translations
|
||||
|
||||
version 2.5.23 (beta only):
|
||||
- Hopefully fix slow playback startup just after Kodi startup
|
||||
- Better, safer way to enter network credentials for Direct Paths
|
||||
- Fix check whether a direct path is accessible
|
||||
- Fix OperationalError: no such table on database reset
|
||||
- Fix widgets not displaying correct playstate after PKC startup
|
||||
- Fix 'NoneType' object has no attribute 'execute' when Plex artwork is not synced and an item is deleted
|
||||
- Update translations
|
||||
- Log whether Plex artwork is synced to Kodi
|
||||
|
||||
version 2.5.22 (beta only):
|
||||
- Fix rare EOFError and PKC starting wrong video as a consequence
|
||||
|
||||
version 2.5.21 (beta only):
|
||||
- Fix KodiVideoDB object has no attribute kodiconn
|
||||
- Fix local variable 'set_api' referenced before assignment
|
||||
|
||||
version 2.5.20 (beta only):
|
||||
- Begin a new transaction when database was locked
|
||||
- Fix browsing to show from info dialog
|
||||
- Fix rare KeyError if user is playing something somewhere else
|
||||
|
||||
version 2.5.19 (beta only):
|
||||
- Fix crash on startup-sync due to missing albums
|
||||
- Fix browsing to show from info dialog
|
||||
|
||||
version 2.5.18 (beta only):
|
||||
- Fix playback start: Don't lock databases when starting playback
|
||||
- Refresh Kodi view only once on full syncs
|
||||
- Ignore playstate updates for full sync time stamps croneter committed
|
||||
- Try even longer to write to Kodi database
|
||||
- Fix some items rarely not being synced
|
||||
|
||||
version 2.5.17 (beta only):
|
||||
- Fix playback not starting for really large libraries
|
||||
|
||||
version 2.5.16 (beta only):
|
||||
- Fix KeyError due to malformed PMS messages
|
||||
- Fix to database parameter must be string
|
||||
|
||||
version 2.5.15 (beta only):
|
||||
- Make PKC potentially compatible with several database schemas
|
||||
- Support for Kodi 18 Leia RC 5.2
|
||||
- Increase number of attempts to write to Kodi DB
|
||||
- Further increase database sync resiliance
|
||||
|
||||
version 2.5.14 (beta only):
|
||||
Fix rare OperationalError: Locked Database
|
||||
|
||||
version 2.5.13 (beta only):
|
||||
- Fix playback not starting up
|
||||
- Fix Plex channels and watch later not working
|
||||
- Hopefully fix playstate not being synced to PMS
|
||||
|
||||
version 2.5.12 (beta only):
|
||||
- WARNING: You will need to reset the Kodi database!
|
||||
- New option to not use Plex artwork
|
||||
- Add-on paths: Fix resume if playback not initiated with PKC
|
||||
- Increase database resiliance with sqlite WAL mode
|
||||
|
||||
version 2.5.11 (beta only):
|
||||
- Direct Paths: Fix AttributeError for widgets
|
||||
|
||||
version 2.5.10 (beta only):
|
||||
- Enable Plex Hub listings to be used for widgets
|
||||
- Finally fix deleteting of items from PMS not working
|
||||
- Catch sqlite OperationalError for websocket messages
|
||||
- Revert "Increase database timeouts"
|
||||
|
||||
version 2.5.9 (beta only):
|
||||
- Compatibility with Kodi 18 RC 4
|
||||
- New setting to escape paths e.g. for HTTP direct paths
|
||||
- Ensure path replacement never contains trailing (back)slash
|
||||
- Leia: fix resetting of videoplayer autoplay next item
|
||||
- Don't store identical show artwork for seasons
|
||||
- Close DB connections while caching images
|
||||
- Increase database timeouts
|
||||
- Improve logging for seasons
|
||||
|
||||
version 2.5.8 (beta only):
|
||||
- Hopefully fix Kodi crashing on playback start
|
||||
- Fix video resuming from old resume point
|
||||
- Fix database is locked
|
||||
- Faster way to initialize playlists on the Plex side
|
||||
- Fix PKC recreating playlists too often
|
||||
- Shutdown playlist sync if necessary
|
||||
|
||||
version 2.5.7 (beta only):
|
||||
- WARNING: You will need to reset the Kodi database!
|
||||
- Increase timeout for database connections
|
||||
- Fix music DB not being wiped on database reset
|
||||
- Improve Plex playQueue resiliance
|
||||
|
||||
version 2.5.6 (beta only):
|
||||
- Fix many items not getting synced
|
||||
- Fix episodes not being synced to due a missing season
|
||||
- Fix some very few items not being synced
|
||||
- Fix ValueError during sync due to missing Plex timestamp
|
||||
- Fix resume for episodes for add-on paths
|
||||
- Fix movies not showing up on switching PMS
|
||||
- Finish full syncs during playbacks, don't start new ones
|
||||
- Fix AttributeError when a playlist disappeared
|
||||
- Close sync dialog if video playback starts
|
||||
- Don't show sync messages while Kodi is playing something
|
||||
- Only marking full sync as successful if that is indeed the case
|
||||
- Optimize code
|
||||
|
||||
version 2.5.5 (beta only):
|
||||
- Fix OperationalError and PKC not starting up
|
||||
|
||||
version 2.5.4 (beta only):
|
||||
- Fix a couple of issues related to episodes
|
||||
- Fix permanent missing library items if PMS failed to send a single response
|
||||
- Fix OperationalError: enforce Kodi restart with clean DB once
|
||||
- Fix switching PMS not recognizing when old PMS is selected
|
||||
- Fix PKC not automatically connecting to changed PMS IP on startup
|
||||
- Remove message "Full library sync finished"
|
||||
- Fix PKC not automatically connecting to changed PMS IP on startup
|
||||
- Remove cProfile program metrics measurements
|
||||
|
||||
version 2.5.3 (beta only):
|
||||
- Fix Plex sections not showing up or disappearing
|
||||
|
||||
version 2.5.2 (beta only):
|
||||
- Rewire library sync
|
||||
- Optimize sqlite transactions
|
||||
- Replace annoying sync message with PKC settings info
|
||||
- Add PKC settings status indication for caching
|
||||
- Fix KeyError when synching playlists
|
||||
- Fix ImportError for Plex Companion gdm issues
|
||||
- Increase database connection cache size
|
||||
- Force-Reboot Kodi immediately if sqlite PRAGMA WAL causes errors
|
||||
- Force a full sync on switching Plex username
|
||||
- Fix wierd behavior upon switching to another PMS
|
||||
- More bugfixes and code optimizations
|
||||
|
||||
version 2.5.1 (beta only):
|
||||
- Fix OSError on resetting the database
|
||||
|
||||
version 2.5.0 (beta only):
|
||||
- Huge rewrite of the sync mechanism - it should now be faster and more stable
|
||||
- Sync huge Plex libraries now: the sync will load all data bit by bit
|
||||
- Rewrote code for the main program loop, reducing the need for separate Python threads
|
||||
- Rewrote and sped up code to access and change Kodi and Plex databases
|
||||
- Fixes to Kodi 18 Leia music library
|
||||
- Tons of other small fixes I can't remember
|
||||
|
||||
version 2.4.10 (beta only):
|
||||
- Use xml.etree.cElementTree whenever possible to avoid memory leaks
|
||||
|
||||
version 2.4.9:
|
||||
- Fix Kodi crashing due to PKC memory leak
|
||||
|
||||
version 2.4.8:
|
||||
- Make 2.4.4-2.4.7 available for everyone
|
||||
|
||||
version 2.4.7 (beta only):
|
||||
- Try to fix PKC for Enigma 2
|
||||
- Fix Kodi 18 wanting to scan tags for songs all the time (you will need to reset the database in the PKC settings)
|
||||
- Optimize resetting of Kodi and Plex databases
|
||||
|
||||
version 2.4.6 (beta only):
|
||||
- Fix PKC not starting up on Enigma
|
||||
- Fix sync issues if video lies in root of file system
|
||||
- Make sure we retain a dummy first music artist entry
|
||||
- Increase logging
|
||||
|
||||
version 2.4.5 (beta only):
|
||||
- Fix playback not starting up at all
|
||||
- Rewire Kodi library refreshs
|
||||
- Wipe Kodi database on first PKC run to more reliably install PKC
|
||||
|
||||
version 2.4.4 (beta only):
|
||||
- Fix rare case when playback would not start-up
|
||||
- Increase logging
|
||||
|
||||
version 2.4.3:
|
||||
- Fix Kodi addons throwing jsonrpc errors (database reset needed)
|
||||
|
||||
version 2.4.2:
|
||||
- Make version 2.4.1 available for everyone
|
||||
|
||||
version 2.4.1 (beta only):
|
||||
- Hopefully fix endless playlist sync loops
|
||||
- Ensure shows are deleted before seasons before episodes
|
||||
- Fix library sync crash on deleting episode with missing season
|
||||
- Fix numbering of already existing playlist files
|
||||
- Optimize logging
|
||||
|
||||
version 2.4.0:
|
||||
- Use pretty Plex dialogs for everyone!
|
||||
|
||||
version 2.3.14 (beta only):
|
||||
- Fix AttributeError on forcing texture caching
|
||||
- Switch to Plex style dialogs
|
||||
- Include PKC info in plex.tv dialogs
|
||||
- Include PKC info in user selection dialog
|
||||
|
||||
version 2.3.13 (beta only):
|
||||
- Pretty Plex dialogs for plex.tv sign-in and user selection
|
||||
- Fix UnicodeDecodeError for PMS with non ASCII chars on local LAN discovery
|
||||
- Fix add-on settings not opening on installation
|
||||
- Greatly speed up deleting of items on the Kodi side
|
||||
- Safely parse XMLs using defusedxml
|
||||
- Fix PKC trying to sync audio playlists even when audio sync disabled
|
||||
- Some code cleanup
|
||||
|
||||
version 2.3.12:
|
||||
- Fix Kodi hanging if media stream selection is aborted
|
||||
- Fix potential sync crash
|
||||
- Revert "Fix Kodi crash by committing to DB frequently"
|
||||
|
||||
version 2.3.11 (beta only):
|
||||
- Fix Kodi crash by committing to DB frequently
|
||||
|
||||
version 2.3.10:
|
||||
- Compatibility with Kodi 18 Leia Beta 1
|
||||
- Update translations
|
||||
- Make version 2.3.9 available for everyone
|
||||
|
||||
version 2.3.9 (beta only):
|
||||
- Fix playback not resuming (Kodi 18 ignores listitem "StartOffset")
|
||||
- Fix playerid not being retrieved for Kodi 18
|
||||
- Prefer local trailers; new setting to list extras instead of playing trailer
|
||||
|
||||
version 2.3.8:
|
||||
- Fix typo
|
||||
- Make version 2.3.4-2.3.7 available for everyone
|
||||
|
||||
version 2.3.7 (beta only):
|
||||
- Fix library sync crash due to exotic playlist characters
|
||||
- Force-deactivate playlist sync for Microsoft UWP for Kodi 18
|
||||
|
||||
version 2.3.6 (beta only):
|
||||
- Fix PKC not starting by decoupling watchdog/subprocess modules
|
||||
|
||||
version 2.3.5 (beta only):
|
||||
- Fix PKC not starting by importing playlist module only when sync enabled
|
||||
|
||||
version 2.3.4 (beta only):
|
||||
- Fix playback sometimes not starting and UnicodeEncodeError for logging
|
||||
|
||||
version 2.3.3:
|
||||
- Choose trailer if several are present (DB reset required)
|
||||
|
||||
version 2.3.2:
|
||||
- Fix casting to PKC failing
|
||||
|
||||
version 2.3.1:
|
||||
- Fix library sync crashing due to Plex photo albums
|
||||
|
||||
version 2.3.0:
|
||||
Major stable version bump. Highlights:
|
||||
- Sync Plex playlists to Kodi and Kodi playlists to Plex!
|
||||
- Support for Plex collection/set artwork
|
||||
- Many bug fixes, especially Plex Companion
|
||||
- Tons of code improvements in the hope that someone else will help with developing PKC
|
||||
Warning: the 2 helper add-ons for movies and tv shows also received an upgrade from 2.0.4 to 2.0.5. If you want to downgrade PKC, be sure to downgrade these add-ons as well!
|
||||
|
||||
version 2.2.18 (beta only):
|
||||
- Fix PKC tv show node "all"
|
||||
- Move PKC playlist shortcut
|
||||
|
||||
version 2.2.17 (beta only):
|
||||
- Access Plex Hubs. Listing will be different depending on Kodi section!
|
||||
- Fix year for songs missing
|
||||
- Fix Plex extras not playing
|
||||
- Fix rare library sync crash
|
||||
|
||||
version 2.2.16 (beta only):
|
||||
- Enable Kodi libraries for Plex Music libraries
|
||||
- New Playlists menu item for video libraries
|
||||
- Only show Plex libraries in the applicable Kodi media category
|
||||
- Optimize code
|
||||
|
||||
version 2.2.15 (beta only):
|
||||
- Fix ImportError on first PKC run
|
||||
|
||||
version 2.2.14 (beta only):
|
||||
- Hopefully fix playlist sync loops
|
||||
|
||||
version 2.2.13 (beta only):
|
||||
- Fix library sync crash
|
||||
- Fix switching to __future__ module
|
||||
- Fix "Prefer Kodi Artwork" toggle doing the exact opposite
|
||||
- Fix "Prefer Kodi artwork" setting not being visible
|
||||
|
||||
version 2.2.12 (beta only):
|
||||
- Fix slow sync. Fix endless sync of corrupted PMS elements
|
||||
- Refactor playlist code
|
||||
- Fix FutureWarning
|
||||
|
||||
version 2.2.11 (beta only):
|
||||
- Fix OnDeck widget for Direct Paths
|
||||
- Fix Plex Companion crashing when connected to Plex Web
|
||||
- Fix Plex Companion crash when connected to Plex Web playing playlist music
|
||||
- Improve Plex playback report when playing music playlist
|
||||
- Improve reliability in Kodi song playback
|
||||
- Catch some errors if user mixes audio and video in Kodi playqueue
|
||||
|
||||
version 2.2.10 (beta only):
|
||||
- Fix playlists getting recreated and deleted in an endless loop
|
||||
- Add some safety nets for playlist sync
|
||||
- Fix FutureWarning
|
||||
- Fix playlist sync settings not disappearing
|
||||
- Optimize code
|
||||
|
||||
version 2.2.9 (beta only):
|
||||
- Hopefully fix Kodi and Plex playlists getting out of sync
|
||||
- Fix and optimize startup of playlist sync
|
||||
- Hide certain playlist settings under certain conditions
|
||||
- Fix errors in Kodi log
|
||||
|
||||
version 2.2.8 (beta only):
|
||||
- Support for Plex collection artwork (PKC settings toggle under Artwork)
|
||||
- Fix hard PKC reset not working (OSError: no such file)
|
||||
- Deduplication
|
||||
- Catch exception
|
||||
- Update translations
|
||||
- Extend Kodi metadata
|
||||
- Update readme
|
||||
|
||||
version 2.2.7 (beta only):
|
||||
- Allow to only sync specific Plex or Kodi playlists
|
||||
- Don't show artwork sync progress, reduce setting-writes
|
||||
- Fix playback sometimes not starting up
|
||||
- Use __future__ for contextmenu.py
|
||||
- Fix imports
|
||||
|
||||
version 2.2.6 (beta only):
|
||||
- Fix default settings string, only show in English, hopefully fixes PKC loosing its settings
|
||||
|
||||
version 2.2.5 (beta only):
|
||||
- Fix AttributeError and add_update has crashed
|
||||
|
||||
version 2.2.4 (beta only):
|
||||
- Fix LibrarySync crashing due to Plex Companion messages
|
||||
|
||||
version 2.2.3 (beta only):
|
||||
- Compatibility with Kodi Krypton Alpha 2
|
||||
- Append tv show and SxxExx to episode playlist entries
|
||||
|
||||
version 2.2.2 (beta only):
|
||||
- Fixes to locking mechanisms which resulted in weird behavior in some cases
|
||||
- Switch to Python __future__ unicode_literals and absolute paths
|
||||
- Fix UnboundLocalError for playlists
|
||||
- Check all Kodi database versions before starting PKC
|
||||
- Fix KeyError on non-PKC playback startup
|
||||
- Speed up subtitle download to Kodi
|
||||
- Update translations
|
||||
- PEP-8 stuff
|
||||
|
||||
version 2.2.1 (beta only):
|
||||
- Fix library sync crash due to PMS sending string, not unicode
|
||||
- Fix playback from playlists for add-on paths
|
||||
- Detect playback from a Kodi playlist for add-on paths - because we need some hacks due to Kodi bugs
|
||||
- Fix add-on paths playstate and Plex Companion for playlists
|
||||
- Fix Kodi telling Plex companion false playqueue position
|
||||
- Don't try to get a Kodi library items for Plex clips
|
||||
- Update translations
|
||||
|
||||
version 2.2.0 (beta only):
|
||||
- Support for syncing Plex playlists to Kodi and vice-versa! (Kodi mixed music and video playlists cannot be supported as Plex does not support them)
|
||||
|
||||
version 2.1.6:
|
||||
- Fix slow sync. Fix endless sync of corrupted PMS elements
|
||||
|
||||
version 2.1.5:
|
||||
- Fix OnDeck widget for Direct Paths
|
||||
|
||||
version 2.1.4:
|
||||
- Fix PKC settings suddenly getting lost
|
||||
- Don't show artwork sync progress, reduce setting-writes
|
||||
|
||||
version 2.1.3:
|
||||
- Fix default settings string, only show in English, hopefully fixes PKC loosing its settings
|
||||
|
||||
version 2.1.2:
|
||||
- Compatibility with Kodi Krypton Alpha 2
|
||||
- Check all Kodi database versions before starting PKC
|
||||
- Fix KeyError on non-PKC playback startup
|
||||
- PEP-8 stuff
|
||||
|
||||
version 2.1.1:
|
||||
- Fix Library Sync crash on Android
|
||||
|
||||
version 2.1.0:
|
||||
Finally a new update for the stable version. You will need to reconnect to your PMS and reset the Kodi database once. Highlights of v2 include:
|
||||
- Support for Plex extras
|
||||
- Huge improvements to Plex Companion
|
||||
- Fixes to Alexa voice control
|
||||
- Kodi 18 Leia Alpha 1 support
|
||||
- Improvements to playback start-up
|
||||
- Improvements to the syncing mechanism, which should get rid of a ton of small bugs
|
||||
- Fixes to widgets and resuming playback
|
||||
- Use of plex.direct paths instead of local IP addresses to ensure the SSL certificates shown by the PMS are deemed valid
|
||||
- Fix Kodi screensaver
|
||||
- Faster PKC startup
|
||||
- And tons of other stuff...</news>
|
||||
</news>
|
||||
</extension>
|
||||
</addon>
|
||||
|
|
|
@ -1,3 +1,27 @@
|
|||
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
|
||||
|
|
|
@ -1150,6 +1150,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"
|
||||
|
@ -1225,6 +1230,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 "
|
||||
|
|
|
@ -1153,6 +1153,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"
|
||||
|
@ -1230,6 +1235,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 "
|
||||
|
|
|
@ -1171,6 +1171,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"
|
||||
|
@ -1246,6 +1251,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: "
|
||||
|
|
1575
resources/language/resource.language.el_GR/strings.po
Normal file
1575
resources/language/resource.language.el_GR/strings.po
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1078,6 +1078,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"
|
||||
|
|
|
@ -1166,6 +1166,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"
|
||||
|
@ -1243,6 +1248,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 "
|
||||
|
|
|
@ -1168,6 +1168,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"
|
||||
|
@ -1245,6 +1250,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 "
|
||||
|
|
|
@ -1166,6 +1166,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"
|
||||
|
@ -1243,6 +1248,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 "
|
||||
|
|
|
@ -1180,6 +1180,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"
|
||||
|
@ -1257,6 +1262,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 "
|
||||
|
|
|
@ -1184,6 +1184,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"
|
||||
|
@ -1261,6 +1266,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 "
|
||||
|
|
|
@ -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"
|
||||
|
@ -51,6 +51,10 @@ msgid ""
|
|||
"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: "
|
||||
|
@ -616,7 +620,7 @@ msgstr "Szinkronizálni kívánt Plex könyvtárak kiválasztása"
|
|||
# PKC Settings - Playback
|
||||
msgctxt "#30525"
|
||||
msgid "Skip intro"
|
||||
msgstr ""
|
||||
msgstr "Bevezető kihagyása"
|
||||
|
||||
# PKC Settings - Playback
|
||||
msgctxt "#30527"
|
||||
|
@ -684,6 +688,8 @@ msgstr "Film-szett/kollekció képek letöltése a FanArtTV-ről"
|
|||
msgctxt "#30541"
|
||||
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"
|
||||
|
@ -983,7 +989,7 @@ msgstr ""
|
|||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
msgstr "Biztonságos karakterek http(s), dav(s) és (s)ftp elérési utakhoz"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
|
@ -1168,6 +1174,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"
|
||||
|
@ -1246,6 +1257,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: "
|
||||
|
|
|
@ -1169,6 +1169,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"
|
||||
|
@ -1246,6 +1251,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 "
|
||||
|
|
1606
resources/language/resource.language.ko_KR/strings.po
Normal file
1606
resources/language/resource.language.ko_KR/strings.po
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1162,6 +1162,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"
|
||||
|
@ -1239,6 +1244,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ą"
|
||||
|
|
|
@ -1144,6 +1144,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"
|
||||
|
@ -1212,6 +1217,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 ""
|
||||
|
|
|
@ -1156,6 +1156,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"
|
||||
|
@ -1234,6 +1239,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 "
|
||||
|
|
|
@ -1149,6 +1149,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"
|
||||
|
@ -1224,6 +1229,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"
|
||||
|
|
1580
resources/language/resource.language.pl_PL/strings.po
Normal file
1580
resources/language/resource.language.pl_PL/strings.po
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1145,6 +1145,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"
|
||||
|
@ -1217,6 +1222,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"
|
||||
|
|
|
@ -1148,6 +1148,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"
|
||||
|
@ -1220,6 +1225,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"
|
||||
|
|
|
@ -1161,6 +1161,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"
|
||||
|
@ -1236,6 +1241,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"
|
||||
|
|
|
@ -1156,6 +1156,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"
|
||||
|
@ -1230,6 +1235,31 @@ 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"
|
||||
|
||||
# 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 "Logga ut Plex Home-användare"
|
||||
|
|
|
@ -1158,6 +1158,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"
|
||||
|
@ -1235,6 +1240,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"
|
||||
|
|
|
@ -1114,6 +1114,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"
|
||||
|
@ -1182,6 +1187,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家庭用户 "
|
||||
|
|
|
@ -1110,6 +1110,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"
|
||||
|
@ -1178,6 +1183,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用戶 "
|
||||
|
|
|
@ -69,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
|
||||
|
||||
|
@ -92,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'
|
||||
|
|
|
@ -48,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]
|
||||
|
|
|
@ -1,13 +1,26 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
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):
|
||||
"""
|
||||
|
@ -37,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())
|
||||
|
@ -105,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())
|
||||
|
||||
|
@ -243,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])
|
||||
|
|
|
@ -45,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
|
||||
|
@ -108,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
|
||||
|
||||
|
|
|
@ -20,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)
|
||||
|
|
|
@ -17,7 +17,6 @@ as well as sources.xml
|
|||
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 xbmcvfs
|
||||
|
@ -27,6 +26,17 @@ KODI_ENCODING = 'utf-8'
|
|||
REGEX_FILE_NUMBERING = re.compile(r'''_(\d\d)\.\w+$''')
|
||||
|
||||
|
||||
def append_os_sep(path):
|
||||
"""
|
||||
Appends either a '\\' or '/' - IRRELEVANT of the host OS!! (os.path.join is
|
||||
dependant on the host OS)
|
||||
"""
|
||||
if '/' in path:
|
||||
return path + '/'
|
||||
else:
|
||||
return path + '\\'
|
||||
|
||||
|
||||
def translate_path(path):
|
||||
"""
|
||||
Returns the XBMC translated path [unicode]
|
||||
|
@ -155,28 +165,47 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
|
|||
[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 doesn’t 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 don’t 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.
|
||||
"""
|
||||
return dir_util.copy_tree(src, dst, *args, **kwargs)
|
||||
return shutil.copytree(src, dst, *args, **kwargs)
|
||||
|
||||
|
||||
def basename(path):
|
||||
|
|
35
resources/lib/pathvalidate/__init__.py
Normal file
35
resources/lib/pathvalidate/__init__.py
Normal 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,
|
||||
)
|
6
resources/lib/pathvalidate/__version__.py
Normal file
6
resources/lib/pathvalidate/__version__.py
Normal 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"
|
137
resources/lib/pathvalidate/_base.py
Normal file
137
resources/lib/pathvalidate/_base.py
Normal 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]
|
147
resources/lib/pathvalidate/_common.py
Normal file
147
resources/lib/pathvalidate/_common.py
Normal 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])
|
16
resources/lib/pathvalidate/_const.py
Normal file
16
resources/lib/pathvalidate/_const.py
Normal 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
|
341
resources/lib/pathvalidate/_filename.py
Normal file
341
resources/lib/pathvalidate/_filename.py
Normal 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)
|
427
resources/lib/pathvalidate/_filepath.py
Normal file
427
resources/lib/pathvalidate/_filepath.py
Normal 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)
|
45
resources/lib/pathvalidate/_ltsv.py
Normal file
45
resources/lib/pathvalidate/_ltsv.py
Normal 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))
|
110
resources/lib/pathvalidate/_symbol.py
Normal file
110
resources/lib/pathvalidate/_symbol.py
Normal 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
|
68
resources/lib/pathvalidate/argparse.py
Normal file
68
resources/lib/pathvalidate/argparse.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
|
||||
"""
|
||||
|
||||
from argparse import ArgumentTypeError
|
||||
|
||||
from ._common import PathType
|
||||
from ._filename import sanitize_filename, validate_filename
|
||||
from ._filepath import sanitize_filepath, validate_filepath
|
||||
from .error import ValidationError
|
||||
|
||||
|
||||
def validate_filename_arg(value: str) -> str:
|
||||
if not value:
|
||||
return ""
|
||||
|
||||
try:
|
||||
validate_filename(value)
|
||||
except ValidationError as e:
|
||||
raise ArgumentTypeError(e)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def validate_filepath_arg(value: str) -> str:
|
||||
if not value:
|
||||
return ""
|
||||
|
||||
try:
|
||||
validate_filepath(value, platform="auto")
|
||||
except ValidationError as e:
|
||||
raise ArgumentTypeError(e)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def sanitize_filename_arg(value: str) -> PathType:
|
||||
if not value:
|
||||
return ""
|
||||
|
||||
return sanitize_filename(value)
|
||||
|
||||
|
||||
def sanitize_filepath_arg(value: str) -> PathType:
|
||||
if not value:
|
||||
return ""
|
||||
|
||||
return sanitize_filepath(value, platform="auto")
|
||||
|
||||
|
||||
def filename(value: PathType) -> PathType: # pragma: no cover
|
||||
# Deprecated
|
||||
try:
|
||||
validate_filename(value)
|
||||
except ValidationError as e:
|
||||
raise ArgumentTypeError(e)
|
||||
|
||||
return sanitize_filename(value)
|
||||
|
||||
|
||||
def filepath(value: PathType) -> PathType: # pragma: no cover
|
||||
# Deprecated
|
||||
try:
|
||||
validate_filepath(value)
|
||||
except ValidationError as e:
|
||||
raise ArgumentTypeError(e)
|
||||
|
||||
return sanitize_filepath(value)
|
74
resources/lib/pathvalidate/click.py
Normal file
74
resources/lib/pathvalidate/click.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
"""
|
||||
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
|
||||
"""
|
||||
|
||||
import click
|
||||
|
||||
from ._common import PathType
|
||||
from ._filename import sanitize_filename, validate_filename
|
||||
from ._filepath import sanitize_filepath, validate_filepath
|
||||
from .error import ValidationError
|
||||
|
||||
|
||||
def validate_filename_arg(ctx, param, value) -> str:
|
||||
if not value:
|
||||
return ""
|
||||
|
||||
try:
|
||||
validate_filename(value)
|
||||
except ValidationError as e:
|
||||
raise click.BadParameter(str(e))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def validate_filepath_arg(ctx, param, value) -> str:
|
||||
if not value:
|
||||
return ""
|
||||
|
||||
try:
|
||||
validate_filepath(value)
|
||||
except ValidationError as e:
|
||||
raise click.BadParameter(str(e))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def sanitize_filename_arg(ctx, param, value) -> PathType:
|
||||
if not value:
|
||||
return ""
|
||||
|
||||
return sanitize_filename(value)
|
||||
|
||||
|
||||
def sanitize_filepath_arg(ctx, param, value) -> PathType:
|
||||
if not value:
|
||||
return ""
|
||||
|
||||
return sanitize_filepath(value)
|
||||
|
||||
|
||||
def filename(ctx, param, value): # pragma: no cover
|
||||
# Deprecated
|
||||
if not value:
|
||||
return None
|
||||
|
||||
try:
|
||||
validate_filename(value)
|
||||
except ValidationError as e:
|
||||
raise click.BadParameter(str(e))
|
||||
|
||||
return sanitize_filename(value)
|
||||
|
||||
|
||||
def filepath(ctx, param, value): # pragma: no cover
|
||||
# Deprecated
|
||||
if not value:
|
||||
return None
|
||||
|
||||
try:
|
||||
validate_filepath(value)
|
||||
except ValidationError as e:
|
||||
raise click.BadParameter(str(e))
|
||||
|
||||
return sanitize_filepath(value)
|
155
resources/lib/pathvalidate/error.py
Normal file
155
resources/lib/pathvalidate/error.py
Normal file
|
@ -0,0 +1,155 @@
|
|||
"""
|
||||
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
|
||||
"""
|
||||
|
||||
import enum
|
||||
from typing import Optional, cast
|
||||
|
||||
from ._common import Platform
|
||||
|
||||
|
||||
@enum.unique
|
||||
class ErrorReason(enum.Enum):
|
||||
"""
|
||||
Validation error reasons.
|
||||
"""
|
||||
|
||||
FOUND_ABS_PATH = "FOUND_ABS_PATH" #: found an absolute path when expecting a file name
|
||||
NULL_NAME = "NULL_NAME" #: empty value
|
||||
INVALID_CHARACTER = "INVALID_CHARACTER" #: found invalid characters(s) in a value
|
||||
INVALID_LENGTH = "INVALID_LENGTH" #: found invalid string length
|
||||
MALFORMED_ABS_PATH = "MALFORMED_ABS_PATH" #: found invalid absolute path format
|
||||
RESERVED_NAME = "RESERVED_NAME" #: found a reserved name by a platform
|
||||
|
||||
|
||||
class ValidationError(ValueError):
|
||||
"""
|
||||
Exception class of validation errors.
|
||||
|
||||
.. py:attribute:: reason
|
||||
|
||||
The cause of the error.
|
||||
|
||||
Returns:
|
||||
:py:class:`~pathvalidate.error.ErrorReason`:
|
||||
"""
|
||||
|
||||
@property
|
||||
def platform(self) -> Platform:
|
||||
return self.__platform
|
||||
|
||||
@property
|
||||
def reason(self) -> Optional[ErrorReason]:
|
||||
return self.__reason
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return self.__description
|
||||
|
||||
@property
|
||||
def reserved_name(self) -> str:
|
||||
return self.__reserved_name
|
||||
|
||||
@property
|
||||
def reusable_name(self) -> bool:
|
||||
return self.__reusable_name
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.__platform = kwargs.pop("platform", None)
|
||||
self.__reason = kwargs.pop("reason", None)
|
||||
self.__description = kwargs.pop("description", None)
|
||||
self.__reserved_name = kwargs.pop("reserved_name", None)
|
||||
self.__reusable_name = kwargs.pop("reusable_name", None)
|
||||
|
||||
try:
|
||||
super().__init__(*args[0], **kwargs)
|
||||
except IndexError:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __str__(self) -> str:
|
||||
item_list = []
|
||||
|
||||
if Exception.__str__(self):
|
||||
item_list.append(Exception.__str__(self))
|
||||
|
||||
if self.reason:
|
||||
item_list.append("reason={}".format(cast(ErrorReason, self.reason).value))
|
||||
if self.platform:
|
||||
item_list.append("target-platform={}".format(self.platform.value))
|
||||
if self.description:
|
||||
item_list.append("description={}".format(self.description))
|
||||
if self.__reusable_name is not None:
|
||||
item_list.append("reusable_name={}".format(self.reusable_name))
|
||||
|
||||
return ", ".join(item_list).strip()
|
||||
|
||||
def __repr__(self, *args, **kwargs):
|
||||
return self.__str__(*args, **kwargs)
|
||||
|
||||
|
||||
class NullNameError(ValidationError):
|
||||
"""
|
||||
Exception raised when a name is empty.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
kwargs["reason"] = ErrorReason.NULL_NAME
|
||||
|
||||
super().__init__(args, **kwargs)
|
||||
|
||||
|
||||
class InvalidCharError(ValidationError):
|
||||
"""
|
||||
Exception raised when includes invalid character(s) within a string.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
kwargs["reason"] = ErrorReason.INVALID_CHARACTER
|
||||
|
||||
super().__init__(args, **kwargs)
|
||||
|
||||
|
||||
class InvalidLengthError(ValidationError):
|
||||
"""
|
||||
Exception raised when a string too long/short.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
kwargs["reason"] = ErrorReason.INVALID_LENGTH
|
||||
|
||||
super().__init__(args, **kwargs)
|
||||
|
||||
|
||||
class ReservedNameError(ValidationError):
|
||||
"""
|
||||
Exception raised when a string matched a reserved name.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
kwargs["reason"] = ErrorReason.RESERVED_NAME
|
||||
|
||||
super().__init__(args, **kwargs)
|
||||
|
||||
|
||||
class ValidReservedNameError(ReservedNameError):
|
||||
"""
|
||||
Exception raised when a string matched a reserved name.
|
||||
However, it can be used as a name.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
kwargs["reusable_name"] = True
|
||||
|
||||
super().__init__(args, **kwargs)
|
||||
|
||||
|
||||
class InvalidReservedNameError(ReservedNameError):
|
||||
"""
|
||||
Exception raised when a string matched a reserved name.
|
||||
Moreover, the reserved name is invalid as a name.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
kwargs["reusable_name"] = False
|
||||
|
||||
super().__init__(args, **kwargs)
|
0
resources/lib/pathvalidate/py.typed
Normal file
0
resources/lib/pathvalidate/py.typed
Normal file
|
@ -402,9 +402,9 @@ def _get_playListVersion_from_xml(playlist, xml):
|
|||
|
||||
Raises PlaylistError if unsuccessful
|
||||
"""
|
||||
playlist.version = utils.cast(int,
|
||||
xml.get('%sVersion' % playlist.kind))
|
||||
if playlist.version is None:
|
||||
try:
|
||||
playlist.version = int(xml.get('%sVersion' % playlist.kind))
|
||||
except (AttributeError, TypeError):
|
||||
raise PlaylistError('Could not get new playlist Version for playlist '
|
||||
'%s' % playlist)
|
||||
|
||||
|
@ -416,6 +416,8 @@ def get_playlist_details_from_xml(playlist, xml):
|
|||
|
||||
Raises PlaylistError if something went wrong.
|
||||
"""
|
||||
if xml is None:
|
||||
raise PlaylistError('No playlist received for playlist %s' % playlist)
|
||||
playlist.id = utils.cast(int,
|
||||
xml.get('%sID' % playlist.kind))
|
||||
playlist.version = utils.cast(int,
|
||||
|
@ -703,8 +705,8 @@ def delete_playlist_item_from_PMS(playlist, pos):
|
|||
playlist.items[pos].id,
|
||||
playlist.repeat),
|
||||
action_type="DELETE")
|
||||
_get_playListVersion_from_xml(playlist, xml)
|
||||
del playlist.items[pos]
|
||||
_get_playListVersion_from_xml(playlist, xml)
|
||||
|
||||
|
||||
# Functions operating on the Kodi playlist objects ##########
|
||||
|
|
|
@ -96,6 +96,13 @@ class Base(object):
|
|||
"""
|
||||
return self.xml.get('type')
|
||||
|
||||
@property
|
||||
def subtype(self):
|
||||
"""
|
||||
Returns the subtype of media, e.g. 'clip' as string or None.
|
||||
"""
|
||||
return self.xml.get('subtype')
|
||||
|
||||
@property
|
||||
def section_id(self):
|
||||
self.check_db()
|
||||
|
|
|
@ -369,7 +369,8 @@ class Media(object):
|
|||
force_check : Will always try to check validity of path
|
||||
Will also skip confirmation dialog if path not found
|
||||
folder : Set to True if path is a folder
|
||||
omit_check : Will entirely omit validity check if True
|
||||
omit_check : Will entirely omit validity check if True. Will
|
||||
be superseded by force_check!
|
||||
"""
|
||||
if path is None:
|
||||
return
|
||||
|
@ -385,7 +386,13 @@ class Media(object):
|
|||
path = 'smb:' + path.replace('\\', '/')
|
||||
if app.SYNC.escape_path:
|
||||
path = utils.escape_path(path, app.SYNC.escape_path_safe_chars)
|
||||
if (app.SYNC.path_verified and not force_check) or omit_check:
|
||||
if force_check:
|
||||
pass
|
||||
elif omit_check:
|
||||
return path
|
||||
elif not app.SYNC.check_media_file_existence:
|
||||
return path
|
||||
elif app.SYNC.path_verified:
|
||||
return path
|
||||
|
||||
# exist() needs a / or \ at the end to work for directories
|
||||
|
|
|
@ -92,7 +92,7 @@ class MyHandler(BaseHTTPRequestHandler):
|
|||
self.send_header('Content-Length', len(body))
|
||||
self.send_header('Connection', "close")
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
self.wfile.write(body.encode('utf-8'))
|
||||
self.wfile.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
|
|
@ -26,7 +26,7 @@ import xbmc
|
|||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
||||
from . import path_ops, variables as v
|
||||
from . import pathvalidate, path_ops, variables as v
|
||||
|
||||
LOG = getLogger('PLEX.utils')
|
||||
|
||||
|
@ -422,29 +422,10 @@ def valid_filename(text):
|
|||
"""
|
||||
Return a valid filename after passing it in [unicode].
|
||||
"""
|
||||
# Get rid of all whitespace except a normal space
|
||||
text = re.sub(r'(?! )\s', '', text)
|
||||
# ASCII characters 0 to 31 (non-printable, just in case)
|
||||
text = re.sub(u'[\x00-\x1f]', '', text)
|
||||
if v.DEVICE == 'Windows':
|
||||
# Whitespace at the end of the filename is illegal
|
||||
text = text.strip()
|
||||
# Dot at the end of a filename is illegal
|
||||
text = re.sub(r'\.+$', '', text)
|
||||
# Illegal Windows characters
|
||||
text = re.sub(r'[/\\:*?"<>|\^]', '', text)
|
||||
elif v.DEVICE == 'MacOSX':
|
||||
# Colon is illegal
|
||||
text = re.sub(r':', '', text)
|
||||
# Files cannot begin with a dot
|
||||
text = re.sub(r'^\.+', '', text)
|
||||
else:
|
||||
# Linux
|
||||
text = re.sub(r'/', '', text)
|
||||
# Ensure that filename length is at most 255 chars (including 3 chars for
|
||||
# filename extension and 1 dot to separate the extension)
|
||||
text = text[:min(len(text), 251)]
|
||||
return text
|
||||
return pathvalidate.sanitize_filename(text,
|
||||
replacement_text='_',
|
||||
platform='auto',
|
||||
max_len=248)
|
||||
|
||||
|
||||
def escape_html(string):
|
||||
|
|
|
@ -84,7 +84,7 @@ COMPANION_PORT = int(_ADDON.getSetting('companionPort'))
|
|||
PKC_MACHINE_IDENTIFIER = None
|
||||
|
||||
# Minimal PKC version needed for the Kodi database - otherwise need to recreate
|
||||
MIN_DB_VERSION = '2.6.8'
|
||||
MIN_DB_VERSION = '3.1.3'
|
||||
|
||||
# Supported databases - version numbers in tuples should decrease
|
||||
SUPPORTED_VIDEO_DB = {
|
||||
|
@ -668,14 +668,10 @@ PLEX_STREAM_TYPE_FROM_STREAM_TYPE = {
|
|||
|
||||
# Encoding to be used for our m3u playlist files
|
||||
# m3u files do not have encoding specified by definition, unfortunately.
|
||||
if DEVICE == 'Windows':
|
||||
M3U_ENCODING = 'mbcs'
|
||||
else:
|
||||
M3U_ENCODING = sys.getfilesystemencoding()
|
||||
if (not M3U_ENCODING or
|
||||
M3U_ENCODING == 'ascii' or
|
||||
M3U_ENCODING == 'ANSI_X3.4-1968'):
|
||||
M3U_ENCODING = 'utf-8'
|
||||
# The filesystem encoding is generally utf-8
|
||||
# For Windows, it can be either utf-8 or mbcs
|
||||
# See https://docs.python.org/3/library/sys.html#sys.getfilesystemencoding
|
||||
M3U_ENCODING = sys.getfilesystemencoding()
|
||||
|
||||
|
||||
def database_paths():
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
<setting id="dbSyncScreensaver" type="bool" label="39062" default="false" /><!--Sync when screensaver is deactivated-->
|
||||
<setting id="dbSyncIndicator" label="30507" type="bool" default="true" /><!-- show syncing progress -->
|
||||
<setting id="playstate_sync_indicator" label="30523" type="bool" default="false" visible="eq(-1,true)" subsetting="true"/><!-- Also show sync progress for playstate and user data -->
|
||||
<setting id="check_media_file_existence" type="bool" label="39075" default="true" /><!--Verify access to media files while synching -->
|
||||
<setting id="syncThreadNumber" type="slider" label="39003" default="10" option="int" range="1,1,30"/><!-- Number of simultaneous download threads -->
|
||||
<setting id="limitindex" type="slider" label="30515" default="200" option="int" range="50,50,1000"/><!-- Maximum items to request from the server at once -->
|
||||
<setting type="lsep" label="$LOCALIZE[136]" /><!-- Playlists -->
|
||||
|
|
Loading…
Reference in a new issue